preface
The last article analyzed how to achieve the “lock” from scratch, although only the simplest lock, but the essence of the “lock” has been extracted, with these knowledge, this will analyze the system to provide the use and implementation of the lock -synchronized keyword. Through this article, you will learn:
1, how to use synchronized 2, synchronized source exploration 3, summary
1. How to use synchronized
Multithreaded access to critical sections
As you can see from the previous article, multithreaded access to critical sections requires locking:
A critical section can be a piece of code or a method.
Synchronized various usage modes
According to the lock function area, it can be divided into two categories:
Modification methods
There are two further types of decorators: instance methods and static methods. Let’s start with instance methods: instance methods
Public class TestSynchronized {private int a = 0; public static void main(String args[]) { final TestSynchronized testSynchronized = new TestSynchronized(); Thread t1 = new Thread(new Runnable() { @Override public void run() { int count = 0; while (count < 10000) { testSynchronized.func1(); count++; } System.out.println("a = " + testSynchronized.getA() + " in thread1"); }}); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { int count = 0; while (count < 10000) { testSynchronized.func1(); count++; } System.out.println("a = " + testSynchronized.getA() + " in thread2"); }}); t2.start(); try { t1.join(); t2.join(); Println ("a = "+ testSynchronized. GetA () +" in mainThread"); system.out.println ("a = "+ testSynchronized. } catch (Exception e) {}} private synchronized void func1() {// modify a++; } private int getA() { return a; }}Copy the code
The above two threads T1 and T2 need to modify the value of the shared variable A and call the object method func1() of TestSynchronized to increment. Func1 () is called 10,000 times per thread, and the thread stops running at the end of the loop. In theory, each thread increments the value of a 10000 times, which means that the final value of a should be: a==20000.
It can be seen that the result of multithreaded access is correct, indicating that the instance method modified by synchronized can correctly realize multithreaded concurrency.
Static methods Let’s look at static methods:
Public class TestSynchronized {private static int a = 0; public static void main(String args[]) { Thread t1 = new Thread(new Runnable() { @Override public void run() { int count = 0; while (count < 10000) { func1(); count++; } System.out.println("a = " + getA() + " in thread1"); }}); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { int count = 0; while (count < 10000) { func1(); count++; } System.out.println("a = " + getA() + " in thread2"); }}); t2.start(); try { t1.join(); t2.join(); System.out.println("a = "+ getA() +" in mainThread"); } catch (Exception e) {}} private static synchronized void func1() {// modify a a++; } private static int getA() { return a; }}Copy the code
Instead of decorating the instance method, you just change a to static and func1() to static, and the final result is the same as the previous instance method. It shows that the static method modified by synchronized can correctly realize multithreading concurrency.
Modify code block
When synchronized modifies methods (static methods/instance methods), apply for the lock before entering the method and release the lock after exiting the method. If you have a method that performs a lot of operations and requires only a small section of concurrent access, you’re overqualified to use synchronized for that small critical section. Synchronized provides a way to modify a block of code for this purpose. By lock type, decorated code blocks also fall into two categories: acquiring object locks
Private static Object Object = new Object(); Private void func1() {private void func1() {int b = 1000; int c = 0; if (c < b) { c++; Synchronized (object) {a++; }}Copy the code
It can be seen that the func1 method has other operations, but it is insensitive to multi-threaded operations. Only the shared variable A requires mutually exclusive access, so only operation A needs to be synchronized. Synchronized (object) Obtains the lock of the instance object.
Let’s see how to use a class lock:
Private void func1() {private void func1() {int b = 1000; int c = 0; if (c < b) { c++; Synchronized (TestSynchronized. Class) {a++; }}Copy the code
Instead of instantiating the object, use TestSynchronized. Class to obtain the TestSynchronized class lock.
summary
The above relationship is shown in a graph:
(Class lock is a Class lock) (Class lock is a Class lock) (Class lock is a Class lock)
Object lock
private void func1() {
synchronized (this) { }
}
private synchronized void func2() {
}
private void func3() {
}
Copy the code
Func1 () and func2() both require an object lock (this refers to the object, the object on which the method is called), so access to them is mutually exclusive, while access to func3() is unaffected.
Kind of lock
private void func1() {
synchronized (TestSynchronized.class) { }
}
private static synchronized void func2() {
}
private static synchronized void func3() {
}
private static void func4() {
}
Copy the code
Func1 (), func2(), and func3() all require a class lock, in this case a TestSynchronized. Class object, so access to them is mutually exclusive, while access to func4() is unaffected.
It follows that:
1. Class lock and object lock do not affect each other. 2
2. Preliminary study of synchronized source code
The above example is inseparable from the synchronized modifier, which is a keyword. How does the JVM recognize this keyword? Synchronized (synchronized)
Modify code block
First to see the Demo:
Public class TestSynchronized {// synchronized int a = 0; Object object = new Object(); public static void main(String args[]) { } private void add() { synchronized (object) { a++; }}}Copy the code
This is the code block decorated with object locks. Now compile it as a.class file, navigate to the TestSynchronized. Java file directory, open the command line, and type the following command:
javac TestSynchronized.java
TestSynchronized. Class will be generated in the same directory as the TestSynchronized. Java file. The.class file is invisible to the naked eye, so decompile it and still use the following command in the same directory:
javap -verbose -p TestSynchronized.class
The command line then outputs a string of results, of course, if you feel uncomfortable to view, you can put the output in a file, using the following command:
javap -verbose -p TestSynchronized.class > mytest.txt
Let’s look at the highlights of the output:
The figure above highlights two instructions: Monitorenter and Monitorexit.
- Monitorenter obtains locks
- Monitorexit: Releases locks
- The operation between the two is the locked critical region
Monitorexit has two, the latter being executed when an exception occurs
Code for monitorenter/ Monitorexit directive
Where is the code for the Monitorenter/Monitorexit directive? There are different explanations on the Internet, I prefer: github.com/farmerjohng… Analysis made by:
- Only the template interpreter is used in Hotspot (templatetable_x86_64.cpp)
, the bytecodeInterpreter (bytecodeinterpreter.cpp) is useless
- The template interpreter is assembly code, bytecode interpreter is implemented in C++, the logic of the two is almost the same, in order to make it easier to read bytecode interpreter for example
Code for monitorenter directive:
In bytecodeInterpreter. Cpp# 1804 rows.
Monitorexit directive: In bytecodeInterpreter. Cpp# 1911 rows.
The monitorenter/ Monitorexit directive is implemented in the monitorenter/ Monitorexit directive.
Modification methods
Let’s start with Demo:
Public class TestSynchronized {// synchronized int a = 0; Object object = new Object(); public static void main(String args[]) { } private synchronized void add() { a++; }}Copy the code
Using the javap directive again, the result is as follows:
Unlike the modifiers, there is no Monitorenter/Monitorexit directive, but the ACC_SYNCHRONIZED tag. How is this interpreted? Take a look at the lock entry and exit corresponding to the code:
Method lock entry
In bytecodeInterpreter. Cpp# 643 rows. The red part on the icon can be seen from the name to determine whether the method is synchronous method, if synchronous method, then the step to obtain the lock. Look for the is_synchronized() function, in method.hpp.
Continue with accessFlags.hpp:
Eventually the JVM. H
As can be seen:
After modifying the synchronized keyword, the decompilated code contains: The ACC_SYNCHRONIZED flag corresponds to JVM_ACC_SYNCHRONIZED in the JVM, and the final use of this parameter is the is_synchronized() function to determine whether it is a synchronized method.
Method lock outlet
After the method is finished, run this code, which determines whether it is a synchronous method, and then release the lock and other operations.
3, summarize
Synchronized modifies code blocks and methods:
Monitorenter and MonitoreXIT instructions will be added before and after the critical section when the code block is modified. ACC_SYNCHRONIZED will be checked if the ACC_SYNCHRONIZED flag exists when the method is modified/exits the method Or ACC_SYNCHRONIZED, which is ultimately all about manipulating objects and acquiring locks.
After understanding the use of synchronized and its source code entry, the working mechanism of synchronized will be explored in depth. The next part will analyze the implementation mechanism of no-lock, biased lock, lightweight lock and heavyweight lock.
This article is based on JDK8.