In order to solve the problems of atomicity, visibility and order in concurrent programming, the Java language provides a series of keywords related to concurrent processing, such as , , , synchronized
and volatile
so final
on concurren包
.
In "In-depth Understanding of Java Virtual Machine", there is such a passage:
synchronized
Keywords can be used as one of the solutions when the three characteristics of atomicity, visibility and order are required, and it seems to be "universal". Indeed, most concurrency control operations can be done using synchronized.
Then, this article revolves around synchronized
it, mainly introducing synchronized
the usage, synchronized
principles, and synchronized
how to provide atomicity, visibility, and order guarantees.
usage of synchronized
synchronized
Is a concurrency control keyword provided by Java. There are two main uses, which are synchronized methods and synchronized code blocks. That is, synchronized
both methods and code blocks can be decorated.
/**
* @author Hollis 18/08/04.
*/
public class SynchronizedDemo {
//同步方法
public synchronized void doSth(){
System.out.println("Hello World");
}
//同步代码块
public void doSth1(){
synchronized (SynchronizedDemo.class){
System.out.println("Hello World");
}
}
}
Modified synchronized
code blocks and methods can only be accessed by a single thread at a time.
The realization principle of synchronized
synchronized
, is a very important keyword in Java to solve data synchronization access under concurrent conditions. synchronized
When we want to ensure that a shared resource is only accessed by one thread at a time, we can use keywords to lock classes or objects in the code .
In [In-depth understanding of multi-threading (1) - Synchronized implementation principle] [2], I have introduced its implementation principle. In order to ensure the integrity of knowledge, here is a brief introduction. For details, please read the original text.
We decompile the above code to get the following code:
public synchronized void doSth();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello World
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public void doSth1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: ldc #5 // class com/hollis/SynchronizedTest
2: dup
3: astore_1
4: monitorenter
5: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #3 // String Hello World
10: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
14: monitorexit
15: goto 23
18: astore_2
19: aload_1
20: monitorexit
21: aload_2
22: athrow
23: return
It can be seen from the decompiled code that: for synchronization methods, JVM uses ACC_SYNCHRONIZED
markers to achieve synchronization. For synchronous code blocks. The JVM uses monitorenter
two monitorexit
instructions to achieve synchronization.
In [The Java® Virtual Machine Specification][3], there is an introduction to the implementation principles of synchronization methods and synchronization code blocks. I translate it into Chinese as follows:
Method-level synchronization is implicit. There will be a flag in the constant pool of the synchronized method
ACC_SYNCHRONIZED
. When a thread wants to access a method, it will check whether it is availableACC_SYNCHRONIZED
. If it is set, it needs to obtain the monitor lock first, then start to execute the method, and release the monitor lock after the method is executed. At this time, if other threads request to execute the method, they will be blocked because they cannot obtain the monitor lock. It is worth noting that if an exception occurs during method execution and the exception is not handled inside the method, the monitor lock will be automatically released before the exception is thrown outside the method.Synchronized code blocks are implemented using
monitorenter
andmonitorexit
two directives. Executingmonitorenter
instructions can be understood as locking, and executionmonitorexit
can be understood as releasing locks. Each object maintains a counter that records the number of times it has been locked. The counter of the unlocked object is 0. When a thread acquires the lock (executionmonitorenter
), the counter increments to 1. When the same thread acquires the lock of the object again, the counter increments again. When the same thread releases the lock (executesmonitorexit
the instruction), the counter is decremented again. when the counter is 0. The lock will be released and other threads can acquire the lock.
No matter it is ACC_SYNCHRONIZED
or monitorenter
, monitorexit
it is implemented based on Monitor. In the Java virtual machine (HotSpot), Monitor is implemented based on C++ and implemented by ObjectMonitor.
Several methods are provided in the ObjectMonitor class, such as enter
, exit
, wait
, notify
, notifyAll
and so on. sychronized
When locking, the enter method of objectMonitor will be called, and the exit method will be called when unlocking. (For details about Monitor, see [In-depth understanding of multithreading (4) - the realization principle of Monitor][4])
synchronized and atomicity
Atomicity means that an operation is uninterruptible, and it must be executed completely, or it will not be executed at all.
What is going on with our multithreading problem in [Java's concurrent programming? ] [5] analyzed: thread is the basic unit of CPU scheduling. The CPU has the concept of time slices, and will perform thread scheduling according to different scheduling algorithms. When a thread starts executing after obtaining the time slice, after the time slice is exhausted, it will lose the right to use the CPU. Therefore, in a multi-threaded scenario, since time slices are rotated between threads, atomicity problems will occur.
In Java, in order to ensure atomicity, two high-level bytecode instructions monitorenter
and are provided monitorexit
. As mentioned earlier, these two bytecode instructions are the corresponding keywords in Java synchronized
.
Through monitorenter
the and monitorexit
instruction, it can be guaranteed that synchronized
the modified code can only be accessed by one thread at a time, and cannot be accessed by other threads until the lock is released. Therefore, it can be used in Java synchronized
to ensure that operations within methods and code blocks are atomic.
When thread 1 executes
monitorenter
the instruction, it will lock the monitor. After locking, other threads cannot obtain the lock unless thread 1 actively unlocks it. Even during execution, for some reason, such as the CPU time slice is exhausted, thread 1 gives up the CPU, but he does not unlock it. And becausesynchronized
the lock is reentrant, the next time slice can only be acquired by himself, and the code will continue to be executed. until all codes are executed. This guarantees atomicity.
synchronized and visibility
Visibility means that when multiple threads access the same variable, one thread modifies the value of the variable, and other threads can immediately see the modified value.
We are in [If someone asks you what the Java memory model is, send him this article. ][1] analyzed: The Java memory model stipulates that all variables are stored in the main memory, and each thread also has its own working memory. The working memory of the thread stores the main memory of the variables used in the thread. Memory copy copy, all operations on variables by threads must be performed in the working memory, and cannot directly read and write the main memory. Different threads cannot directly access variables in each other's working memory, and the transfer of variables between threads requires data synchronization between their own working memory and main memory. Therefore, it may happen that thread 1 changes the value of a variable, but thread 2 is not visible.
As we mentioned earlier, synchronized
the modified code will be locked when it starts to execute, and will be unlocked after execution is completed. In order to ensure visibility, there is a rule like this: before unlocking a variable, the variable must be synchronized back to main memory. After unlocking in this way, subsequent threads can access the modified value.
Therefore, the value of the object locked by the synchronized keyword is visible.
synchronized and orderly
Sequence means that the order of program execution is executed in the order of code.
We are in [If someone asks you what the Java memory model is, send him this article. ][1] analyzed: In addition to the introduction of time slices, due to processor optimization and instruction rearrangement, the CPU may also execute the input code out of order, such as load->add->save may be optimized to load ->save->add. This is where there may be an orderly problem.
It should be noted here that synchronized
instruction rearrangement and processor optimization cannot be prohibited. That is, synchronized
the above-mentioned problems cannot be avoided.
So, why do you say that synchronized
order guarantees are also provided?
This is to expand the concept of orderliness. The natural order in Java programs can be summed up in one sentence: If observed in this thread, all operations are naturally ordered. If one thread observes another, all operations are unordered.
The above sentence is also the original sentence in "In-depth Understanding of Java Virtual Machine", but how to understand it? Zhou Zhiming did not explain in detail. Here I will briefly expand, which is actually as-if-serial语义
related to .
as-if-serial
Semantics means: no matter how reordering (compiler and processor to improve parallelism), the execution result of a single-threaded program cannot be changed. No matter how compilers and processors optimize, as-if-serial
the semantics must be respected.
as-if-serial语义
I won’t go into details here as-if-serial语义
. Simply put, it is guaranteed that in a single thread, there are certain restrictions on instruction rearrangement, and as long as the compiler and processor comply with this semantics, then it can be considered that the single thread program is executed in order. of. Of course, there are actually rearrangements, but we don't need to care about the interference of such rearrangements.
So, due to synchronized
the modified code, it can only be accessed by the same thread at the same time. Then it is single-threaded execution. Therefore, its order can be guaranteed.
synchronized and lock optimization
synchronized
The usage, principle and effect on concurrent programming introduced earlier . is a great keyword to use.
synchronized
In fact, it is implemented with the help of Monitor. The method of objectMonitor will be called when locking enter
, and the method will be called when unlocking exit
. In fact, only before JDK1.6, the implementation of synchronized will directly call the enter
sum of ObjectMonitor exit
. This kind of lock is called a heavyweight lock.
Therefore, in JDK1.6, a lot of optimizations have been made on locks, and then there are lightweight locks, biased locks, lock elimination, adaptive spin locks, and lock coarsening (spin locks exist in 1.4, but the default is turned off, jdk1.6 is turned on by default), these operations are to share data more efficiently between threads and solve competition problems.
For spin locks, lock coarsening and lock elimination, please refer to [In-depth understanding of multithreading (5) - lock optimization technology for Java virtual machines][6]. Regarding lightweight locks and biased locks, they are already in scheduling planning , I will introduce a separate article later, and will exclusively publish it on my blog (http://www.hollishuang.com) and official account (Hollis), so stay tuned.
Well, regarding synchronized
keywords, we introduced their usage, principles, and how to ensure atomicity, sequence, and visibility. At the same time, we also extended the information and thinking related to lock optimization.