Is it going to be weekly? No, no, no
This article is basically about synchronized, hope I write more simple and easy to understand than others, ha ha ha. In fact, there are many knowledge points about multi-threading, no matter which language is so, so later will be interspersed with other knowledge points to explain, otherwise it is too boring.
Thread insecurity
There is such a sentence in “Java Concurrent Programming Field”
When multiple threads access a class, it is thread-safe if the class behaves correctly regardless of the scheduling and interchangeability of those threads in the runtime environment and requires no additional synchronization and no other coordination by the caller code.
In layman’s terms, to be code thread safe means to ensure that state is accessed without error, and the state of an object generally refers to data. But data is mostly shared and mutable.
In fact, in our daily development, the most encountered thread insecurity is more about whether the modification of a variable can meet the expectation, so the following examples focus more on the simple guarantee that the modification of a variable is safe.
Let’s start with the famous i++ case where it’s unsafe
package concurrent.safe;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SynchronizedDemo {
// Normal methods, code blocks, static methods
public static void main(String[] args) throws InterruptedException {
int threadSize = 1000;
ThreadAddExample example = new ThreadAddExample();
// Ensure that the main thread ends after each child thread
final CountDownLatch countDownLatch = new CountDownLatch(threadSize);
// Start a thread pool in an unrecommended way
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < threadSize; i++) {
executorService.execute(() -> {
example.add();
countDownLatch.countDown();
});
}
countDownLatch.await();
// Close the thread pool, otherwise it will block foreverexecutorService.shutdown(); System.out.println(example.get()); }}class ThreadAddExample {
private static int cnt = 0;
public void add(a) {
cnt++;
}
public int get(a) {
returncnt; }}Copy the code
The whole process is to create a thread pool and then execute 1000 tasks, each of which performs ++ operations on the CNT and finally reads the CNT. However, there is no protection, so there must be two threads modifying the CNT variable at the same time, so that the modification of one thread is invalid. In this case, CNT cannot be equal to 1000.
If you look at the results, you can see that the results are as expected, sometimes with a lot of errors, sometimes with a lot of errors, mainly depending on the CPU.
usage
In view of the above situation, it is necessary to use certain synchronization measures to ensure that the implementation result is correct, and the synchronized keyword is mainly used in this paper
The code block
Add a method to the above class
public void addWithBlockSync1(a) {
synchronized(ThreadAddExample.class) { cnt++; }}Copy the code
The class ThreadAddExample is used as the lock, so that each thread must obtain this class to modify the CNT resource. The final result is as follows: no matter how many times the thread runs, the result is 1000, indicating that no two or more threads modify the CNT at the same time.
Let’s take a look at another example of using synchronized to surround a block of code
public void addWithBlockSync2(a) {
synchronized (newThreadAddExample()) { cnt++; }}Copy the code
Note that the lock used here is an instance of the thread’s own new
I wonder why threads are not safe.
The first case is like a room with only one door, and each thread can only enter the room if it has the same key, so threads are safe. In the second case, the thread creates an instance of itself, which is equivalent to creating multiple doors for the thread. The thread only needs to open its own door to enter the room.
What if the lock object is not new ThreadAddExample() but this
public void addWithBlockSync3(a) {
synchronized (this) { cnt++; }}Copy the code
The result is that the thread is safe because the lock is this, but we only new one object in the whole process.
Common methods
Another approach is to add the synchronized keyword directly to the method body
public synchronized void addWithMethodSync(a) {
cnt++;
}
Copy the code
You can see that thread safety can also be achieved
A static method
In addition to the above methods, another common approach is to use keywords in static methods
public synchronized static void addWithStaticSync(a) {
cnt++;
}
Copy the code
The results are as follows:
The principle of
Use javap-verbose xxx.class to view the bytecode file
Synchronized code block
As you can see, it is the Monitorenter and MonitoreXit that keep the thread safe, whether it is using any new object as a lock or this single lock.
Method body
The flags field contains the ACC_SYNCHRONIZED flag, and the monitor is synchronized.
Object head
Let’s briefly talk about the composition of the object head, but there seems to be no objective representation of the composition, and it’s just a structure that most books and blogs agree on, right
Remember that an object consists of an object header, instance data, and an alignment population, and that the object header contains a pointer to a Monitor, which can be considered a heavyweight lock.
The monitor data structure is in the SOURCE code for the JVM, specifically hotspot, with important variable comments written at the end.
Because each object has an object header, and each object header has a pointer to a Monitor, each object can act as a lock; Monitorenter and MonitoreXit are used in the decomcompilation because monitor has a count field. Monitorenter and Monitorexit are used in the decomcompilation to ensure that the lock is released in the event of an exception.