I. concept
synchronized is a key word in Java. It uses the lock mechanism to achieve synchronization.
The locking mechanism has the following two characteristics:
-
Mutex: that is, only one thread is allowed to hold a certain object lock at the same time. Through this feature, the coordination mechanism in multithreading is realized, so that only one thread can access the code block (composite operation) to be synchronized at the same time. Mutual exclusion is also often called the atomicity of operation.
-
Visibility: it must be ensured that the changes made to the shared variable before the lock is released are visible to another thread that subsequently obtains the lock (that is, the value of the latest shared variable should be obtained when obtaining the lock), otherwise another thread may continue to operate on a copy of the local cache, causing inconsistency.
Object lock and class lock
1. object lock
In Java, every object has a monitor object, which is actually the lock of Java object, usually called "built-in lock" or "object lock". Class can have more than one object, so each object has its own object lock, which does not interfere with each other.
2. kinds of locks
In Java, there is also a lock for each Class, which can be called "Class lock". In fact, Class lock is implemented by object lock, that is, Class object lock. There is only one Class object per Class, so there is only one Class lock per Class.
3. Usage and classification of synchronized
The usage of synchronized can be classified from two dimensions:
1. Classification according to decorated objects
synchronized to decorate methods and code blocks
-
Decorated code block
-
synchronized(this|object) {}
-
Synchronized (class. class) {}
-
-
Modification method
-
Modifying non static methods
-
Modifying static methods
-
2. According to the acquired lock classification
-
Get object lock
-
synchronized(this|object) {}
-
Modifying non static methods
-
-
Get class lock
-
Synchronized (class. class) {}
-
Modifying static methods
-
IV. detailed explanation of the usage of synchronized
Here, we analyze the usage of synchronized according to the obtained lock classification.
1. Get object lock
1.1 for the same object
- Synchronization thread class:
class SyncThread implements Runnable { @Override public void run() { String threadName = Thread.currentThread().getName(); if (threadName.startsWith("A")) { async(); } else if (threadName.startsWith("B")) { sync1(); } else if (threadName.startsWith("C")) { sync2(); } } /** * Asynchronous method */ private void async() { try { System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } /** * Method has synchronized(this|object) {} synchronized code block */ private void sync1() { System.out.println(Thread.currentThread().getName() + "_Sync1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); synchronized (this) { try { System.out.println(Thread.currentThread().getName() + "_Sync1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Sync1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * synchronized Modifying non static methods */ private synchronized void sync2() { System.out.println(Thread.currentThread().getName() + "_Sync2: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { System.out.println(Thread.currentThread().getName() + "_Sync2_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Sync2_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } }Copy code
- Test code:
public class SyncTest { public static void main(String... args) { SyncThread syncThread = new SyncThread(); Thread A_thread1 = new Thread(syncThread, "A_thread1"); Thread A_thread2 = new Thread(syncThread, "A_thread2"); Thread B_thread1 = new Thread(syncThread, "B_thread1"); Thread B_thread2 = new Thread(syncThread, "B_thread2"); Thread C_thread1 = new Thread(syncThread, "C_thread1"); Thread C_thread2 = new Thread(syncThread, "C_thread2"); A_thread1.start(); A_thread2.start(); B_thread1.start(); B_thread2.start(); C_thread1.start(); C_thread2.start(); } }Copy code
- Operation result:
B_thread2_Sync1: 14:44:20 A_thread1_Async_Start: 14:44:20 B_thread1_Sync1: 14:44:20 C_thread1_Sync2: 14:44:20 A_thread2_Async_Start: 14:44:20 C_thread1_Sync2_Start: 14:44:20 A_thread1_Async_End: 14:44:22 A_thread2_Async_End: 14:44:22 C_thread1_Sync2_End: 14:44:22 B_thread1_Sync1_Start: 14:44:22 B_thread1_Sync1_End: 14:44:24 B_thread2_Sync1_Start: 14:44:24 B_thread2_Sync1_End: 14:44:26 C_thread2_Sync2: 14:44:26 C_thread2_Sync2_Start: 14:44:26 C_thread2_Sync2_End: 14:44:28 Copy code
-
Results analysis:
-
There is no synchronous code block in the class A thread access method, and the class A thread is asynchronous. Therefore, when there is A synchronous code block of the thread access object, another thread can access the asynchronous code block of the object:
A_thread1_Async_Start: 14:44:20 A_thread2_Async_Start: 14:44:20 A_thread1_Async_End: 14:44:22 A_thread2_Async_End: 14:44:22 Copy code
-
There are synchronization code blocks in the methods accessed by class B threads. Class B threads are synchronized. One thread accesses the synchronization code block of the object, and the other thread accesses the synchronization code block of the object will be blocked:
B_thread1_Sync1_Start: 14:44:22 B_thread1_Sync1_End: 14:44:24 B_thread2_Sync1_Start: 14:44:24 B_thread2_Sync1_End: 14:44:26 Copy code
-
Code other than synchronized(this|object) {} code block {} is still asynchronous:
B_thread2_Sync1: 14:44:20 B_thread1_Sync1: 14:44:20 Copy code
-
The C-type thread accesses the synchronized modified non static method. The C-type thread is synchronous. When one thread accesses the object's synchronous generation method, the other thread accesses the object's synchronous method will be blocked:
C_thread1_Sync2_Start: 14:44:20 C_thread1_Sync2_End: 14:44:22 C_thread2_Sync2_Start: 14:44:26 C_thread2_Sync2_End: 14:44:28 Copy code
-
Synchronized modifies a non static method. The scope of action is the entire method. Therefore, all the code in the method is synchronized:
C_thread1_Sync2: 14:44:20 C_thread2_Sync2: 14:44:26 Copy code
-
It can be seen from the results that class B and class C threads execute in sequence. The synchronized(this|object) {} code block in the * * class and the lock obtained by the synchronized modified non static method are the same lock, that is, the object lock of the object of this class. **So class B threads and class C threads are also synchronized:
B_thread1_Sync1_Start: 14:44:22 B_thread1_Sync1_End: 14:44:24 C_thread1_Sync2_Start: 14:44:20 C_thread1_Sync2_End: 14:44:22 B_thread2_Sync1_Start: 14:44:24 B_thread2_Sync1_End: 14:44:26 C_thread2_Sync2_Start: 14:44:26 C_thread2_Sync2_End: 14:44:28 Copy code
-
1.2 for different objects
- Modify the test code to:
public class SyncTest { public static void main(String... args) { Thread A_thread1 = new Thread(new SyncThread(), "A_thread1"); Thread A_thread2 = new Thread(new SyncThread(), "A_thread2"); Thread B_thread1 = new Thread(new SyncThread(), "B_thread1"); Thread B_thread2 = new Thread(new SyncThread(), "B_thread2"); Thread C_thread1 = new Thread(new SyncThread(), "C_thread1"); Thread C_thread2 = new Thread(new SyncThread(), "C_thread2"); A_thread1.start(); A_thread2.start(); B_thread1.start(); B_thread2.start(); C_thread1.start(); C_thread2.start(); } }Copy code
- Operation result:
A_thread2_Async_Start: 15:01:34 C_thread2_Sync2: 15:01:34 B_thread2_Sync1: 15:01:34 C_thread1_Sync2: 15:01:34 B_thread2_Sync1_Start: 15:01:34 B_thread1_Sync1: 15:01:34 C_thread1_Sync2_Start: 15:01:34 A_thread1_Async_Start: 15:01:34 C_thread2_Sync2_Start: 15:01:34 B_thread1_Sync1_Start: 15:01:34 C_thread1_Sync2_End: 15:01:36 A_thread1_Async_End: 15:01:36 C_thread2_Sync2_End: 15:01:36 B_thread2_Sync1_End: 15:01:36 B_thread1_Sync1_End: 15:01:36 A_thread2_Async_End: 15:01:36 Copy code
-
Results analysis:
- The synchronized(this|object) {} code block and synchronized decorated nonstatic method of two threads accessing different objects are asynchronous, and the object locks of different objects of the same class do not interfere with each other.
2 acquire class lock
2.1 for the same object
- Synchronization thread class:
class SyncThread implements Runnable { @Override public void run() { String threadName = Thread.currentThread().getName(); if (threadName.startsWith("A")) { async(); } else if (threadName.startsWith("B")) { sync1(); } else if (threadName.startsWith("C")) { sync2(); } } /** * Asynchronous method */ private void async() { try { System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } /** * Method has synchronized (class. class) {} synchronization code block */ private void sync1() { System.out.println(Thread.currentThread().getName() + "_Sync1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); synchronized (SyncThread.class) { try { System.out.println(Thread.currentThread().getName() + "_Sync1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Sync1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * synchronized Modifying static methods */ private synchronized static void sync2() { System.out.println(Thread.currentThread().getName() + "_Sync2: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { System.out.println(Thread.currentThread().getName() + "_Sync2_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Sync2_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } }Copy code
- Test code:
public class SyncTest { public static void main(String... args) { SyncThread syncThread = new SyncThread(); Thread A_thread1 = new Thread(syncThread, "A_thread1"); Thread A_thread2 = new Thread(syncThread, "A_thread2"); Thread B_thread1 = new Thread(syncThread, "B_thread1"); Thread B_thread2 = new Thread(syncThread, "B_thread2"); Thread C_thread1 = new Thread(syncThread, "C_thread1"); Thread C_thread2 = new Thread(syncThread, "C_thread2"); A_thread1.start(); A_thread2.start(); B_thread1.start(); B_thread2.start(); C_thread1.start(); C_thread2.start(); } }Copy code
- Operation result:
B_thread1_Sync1: 15:08:13 C_thread1_Sync2: 15:08:13 B_thread2_Sync1: 15:08:13 A_thread1_Async_Start: 15:08:13 C_thread1_Sync2_Start: 15:08:13 A_thread2_Async_Start: 15:08:13 C_thread1_Sync2_End: 15:08:15 A_thread2_Async_End: 15:08:15 A_thread1_Async_End: 15:08:15 B_thread2_Sync1_Start: 15:08:15 B_thread2_Sync1_End: 15:08:17 B_thread1_Sync1_Start: 15:08:17 B_thread1_Sync1_End: 15:08:19 C_thread2_Sync2: 15:08:19 C_thread2_Sync2_Start: 15:08:19 C_thread2_Sync2_End: 15:08:21 Copy code
-
Results analysis:
- It can be seen from the results that in the case of the same object, the behavior of synchronized (class) {} code block or synchronized decorated static method is consistent with that of synchronized(this|object) {} code block and synchronized decorated non static method.
2.2 for different objects
- Modify the test code to:
public class SyncTest { public static void main(String... args) { Thread A_thread1 = new Thread(new SyncThread(), "A_thread1"); Thread A_thread2 = new Thread(new SyncThread(), "A_thread2"); Thread B_thread1 = new Thread(new SyncThread(), "B_thread1"); Thread B_thread2 = new Thread(new SyncThread(), "B_thread2"); Thread C_thread1 = new Thread(new SyncThread(), "C_thread1"); Thread C_thread2 = new Thread(new SyncThread(), "C_thread2"); A_thread1.start(); A_thread2.start(); B_thread1.start(); B_thread2.start(); C_thread1.start(); C_thread2.start(); } }Copy code
- Operation result:
A_thread2_Async_Start: 15:17:28 B_thread2_Sync1: 15:17:28 A_thread1_Async_Start: 15:17:28 B_thread1_Sync1: 15:17:28 C_thread1_Sync2: 15:17:28 C_thread1_Sync2_Start: 15:17:28 C_thread1_Sync2_End: 15:17:30 A_thread2_Async_End: 15:17:30 B_thread1_Sync1_Start: 15:17:30 A_thread1_Async_End: 15:17:30 B_thread1_Sync1_End: 15:17:32 B_thread2_Sync1_Start: 15:17:32 B_thread2_Sync1_End: 15:17:34 C_thread2_Sync2: 15:17:34 C_thread2_Sync2_Start: 15:17:34 C_thread2_Sync2_End: 15:17:36 Copy code
-
Results analysis:
- Two threads access the synchronized (class. class) {} code block of different objects or the synchronized decorated static method. In the class, the synchronized (class. class) {} code block and the synchronized modified static method get the lock of class lock. Class locks are the same for different objects of the same class.
What happens when there are synchronized (class. class) {} code blocks or synchronized modified static methods and synchronized(this|object) {} code blocks and synchronized modified non static methods in the three classes?
- Modify synchronization thread class:
class SyncThread implements Runnable { @Override public void run() { String threadName = Thread.currentThread().getName(); if (threadName.startsWith("A")) { async(); } else if (threadName.startsWith("B")) { sync1(); } else if (threadName.startsWith("C")) { sync2(); } } /** * Asynchronous method */ private void async() { try { System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } /** * synchronized Modifying non static methods */ private synchronized void sync1() { try { System.out.println(Thread.currentThread().getName() + "_Sync1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Sync1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } /** * synchronized Modifying static methods */ private synchronized static void sync2() { try { System.out.println(Thread.currentThread().getName() + "_Sync2_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Sync2_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } }Copy code
- Modify test code:
public class SyncTest { public static void main(String... args) { Thread B_thread1 = new Thread(syncThread, "B_thread1"); Thread C_thread1 = new Thread(syncThread, "C_thread1"); B_thread1.start(); C_thread1.start(); } }Copy code
- Operation result:
B_thread1_Sync1_Start: 15:35:21 C_thread1_Sync2_Start: 15:35:21 B_thread1_Sync1_End: 15:35:23 C_thread1_Sync2_End: 15:35:23 Copy code
-
Operation result analysis:
- From the results, we can see that class B threads and class C threads are asynchronous, that is, synchronized decorated static methods and synchronized decorated non static methods are asynchronous, and the same is true for synchronized (class) {} code blocks and synchronized(this|object) {} code blocks. So object lock and class lock are independent and do not interfere with each other.
4 supplement
-
The synchronized keyword cannot inherit.
For the synchronized decorated method in the parent class, when the child class overwrites the method, it is not synchronized by default, and the synchronized keyword must be used for the displayed one.
-
The synchronized keyword cannot be used when defining interface methods.
-
Constructor cannot use synchronized keyword, but synchronized code block can be used for synchronization.
5. principle
Explore the implementation mechanism of synchronized, how Java optimizes it, lock optimization mechanism, lock storage structure and upgrade process;
When a thread accesses a synchronization code block, it first needs to get a lock to execute the synchronization code. When it exits or throws an exception, it must release the lock. How does it implement this mechanism? Let's start with a simple code:
public class SynchronizedTest {
public synchronized void test1(){
}
public void test2(){
synchronized (this){
}
}
}
Using javap tools to view the generated class file information to analyze the implementation of synchronization
As can be seen from the above, the synchronization code block is implemented by using the monitorenter and monitorexit instructions. The synchronization method (which can't be seen here, it needs to see the JVM's underlying implementation) relies on the ACC х synchronized implementation on the method modifier.
Synchronization code block: the monitorcenter instruction is inserted at the beginning of the synchronization code block, and the monitorexit instruction is inserted at the end of the synchronization code block. The JVM needs to ensure that each monitorcenter has a monitorexit corresponding to it. Any object has a monitor associated with it, and when a monitor is held, it will be locked. When the thread executes the monitorcenter instruction, it will try to obtain the corresponding monitor ownership of the object, that is, it will try to obtain the lock of the object.
Synchronization method: the synchronized method will be translated into common method call and return instructions, such as invokevitual and areturn instructions. There is no special instruction at the VM bytecode level to implement the method modified by synchronized. Instead, in the method table of the Class file, the synchronized flag position 1 in the access flag field of the method indicates that the method is a synchronized method. And use the object calling the method or the Class to which the method belongs to to to represent Klass as the lock object in the internal object of the JVM. (from: http://www.cnblogs.com/javaminer/p/3889023.html)
Original link: https://blog.csdn.net/chenssy/article/details/54883355
Let's continue to analyze, but we need to understand two important concepts before we go deep: Java object header and Monitor.
Java object header, monitor
Java object headers and monitor s are the foundation for implementing synchronized! The following two concepts are introduced in detail.
Java object header
The lock used for synchronized exists in the Java object header, so what is the Java object header? The object header of Hotspot virtual machine mainly includes two parts of data: Mark Word (mark field) and Klass Pointer (type pointer). Klass Point is the pointer to the class metadata of the object. The virtual machine uses this pointer to determine which class instance the object is. Mark Word is used to store the runtime data of the object itself. It is the key to realize lightweight lock and biased lock, so the following will focus on it
Mark Word.
Mark Word is used to store runtime data of the object itself, such as HashCode, GC generation age, lock status flag, lock held by thread, biased thread ID, biased timestamp, etc. The Java object header generally has two machine codes (in 32-bit virtual machine, one machine code is equal to 4 bytes, that is, 32bit). However, if the object is an array type, three machine codes are needed. Because the JVM virtual machine can determine the size of the Java object through the metadata information of the Java object, but it cannot confirm the size of the array from the metadata of the array, so it uses one block to record. Array length. The following figure shows the storage structure of Java object header (32-bit virtual machine):
Object header information is an additional storage cost independent of the data defined by the object itself. However, considering the space efficiency of the virtual machine, Mark Word is designed as a non fixed data structure to store as much data as possible in a very small space memory. It will reuse its own storage space according to the state of the object, that is to say, Mark Word will change with the operation of the program. The change status is as follows (32-bit virtual machine):
The Java object header is briefly introduced. Let's look at Monitor next.
Monitor
What is Monitor? We can understand it as a synchronization tool or a synchronization mechanism, which is usually described as an object.
Just like everything is an object, all Java objects are born monitors. Every Java object has the potential to become a Monitor. Because in the design of Java, every Java object comes out of its womb with an invisible lock, which is called an internal lock or Monitor lock.
Monitor is a thread private data structure. Each thread has a list of available monitor record s and a global list of available records. Each locked object is associated with a monitor (LockWord in MarkWord of the object header points to the starting address of the monitor). At the same time, there is an Owner field in the monitor to store the unique ID of the thread that owns the lock, indicating that the lock is occupied by this thread. Its structure is as follows:
Owner: NULL at the beginning indicates that no thread currently owns the monitor record. When the thread successfully owns the lock, the thread unique ID is saved. When the lock is released, it is set to NULL.
EntryQ: associate a system semaphore to block all threads that attempt to lock monitor record.
RcThis: indicates the number of all threads blocked or waiting on the monitor record.
Nest: the count used to implement the reentry lock.
HashCode: saves the HashCode value copied from the object header (possibly also GC age).
Candidate: used to avoid unnecessary blocking or waiting for the thread to wake up, because only one thread can successfully own the lock at a time. If the previous thread that releases the lock wakes up all the threads that are blocking or waiting for each time, it will cause unnecessary context switching (from blocking to ready and then blocked due to the failure of competing lock), resulting in serious performance degradation. Candidate has only two possible values, 0, indicating that there is no thread to wake up 1, indicating that a successor thread is to wake up to compete for the lock.
Excerpt: implementation principle and application of synchronized in Java)
We know that synchronized is a heavyweight lock with low efficiency. At the same time, this concept has always been in our mind. However, in jdk 1.6, we have carried out various optimizations on the implementation of synchronized, which makes it not so heavy. What are the optimizations adopted by the JVM?
Lock optimization
jdk1.6 introduces a lot of optimizations to the implementation of lock, such as spin lock, adaptive spin lock, lock elimination, lock coarsening, biased lock, lightweight lock and other technologies to reduce the cost of lock operation.
There are four kinds of lock states: no lock state, biased lock state, lightweight lock state and heavyweight lock state. They will gradually upgrade with the fierce competition. Note that locks can be upgraded and not degraded. This strategy is to improve the efficiency of obtaining and releasing locks.
Spin lock
The blocking and wake-up of threads need CPU to change from user state to core state. Frequent blocking and wake-up is a heavy burden for CPU, which will inevitably bring great pressure to the concurrent performance of the system. At the same time, we find that in many applications, the lock state of the object lock will only last for a short period of time. It is not worth blocking and waking threads frequently for this short period of time. So we introduce spin lock.
What is spin lock?
The so-called spin lock is to let the thread wait for a period of time and not be suspended immediately, to see whether the thread holding the lock will release the lock quickly. How to wait? A meaningless cycle is enough.
Spin wait can't replace blocking, not to mention the requirement for the number of processors (multi-core, it seems that there is no single core processor now). Although it can avoid the cost of thread switching, it takes up the processor's time. If the thread holding the lock releases the lock quickly, the spin efficiency will be very good. On the contrary, the spinning thread will consume the processing resources in vain. It will not do any meaningful work. It is typical of occupying the pit, which will lead to performance waste. Therefore, there must be a limit to the waiting time (the number of spins) of the spin. If the spin exceeds the defined time and still fails to acquire the lock, it should be suspended.
Spin lock is introduced in JDK 1.4.2. It is closed by default, but can be opened by - XX: + usepinning. It is opened by default in JDK 1.6. At the same time, the default number of spins is 10, which can be adjusted by the parameter - XX:PreBlockSpin;
If you adjust the spin times of the spin lock through the parameter - XX:preBlockSpin, it will bring a lot of inconvenience. If I adjust the parameter to 10, but many threads of the system release the lock when you just exit (if you spin one or two times more, you can get the lock), are you embarrassed? So JDK 1.6 introduces self-adaptive spin lock, which makes virtual opportunity more and more intelligent.
Adaptive spin lock
JDK 1.6 introduces a smarter spin lock, the adaptive spin lock. The so-called adaptive means that the number of spins is no longer fixed. It is determined by the spin time of the previous lock and the state of the lock owner. How does it do it? If the thread spins successfully, the number of spins will be more next time. Because the virtual machine thinks that since it succeeded last time, the spin is likely to succeed again, and it will allow more spins to wait. On the contrary, if few spins can succeed in a lock, the number of spins will be reduced or even omitted in the future or when the lock is in progress, so as to avoid wasting processor resources.
With the self-adaptive spin lock, with the continuous improvement of program operation and performance monitoring information, the virtual machine will predict the status of program lock more and more accurately, and the virtual opportunity will become more and more intelligent.
Lock elimination
In order to ensure the integrity of data, we need to synchronize and control this part of operations. However, in some cases, the JVM detects that there is no competition for shared data, which is that the JVM will eliminate these synchronization locks. Lock elimination is based on data support of escape analysis.
If there is no competition, why lock? So lock elimination can save meaningless time of requesting lock. Whether the variables escape or not needs to be determined by data flow analysis for the virtual machine, but is it not clear for our programmers? Will we add synchronization before we know that there is no data competition? But sometimes programs are not what we think? Although we don't show the use of locks, when we use some built-in API s of JDK, such as StringBuffer, Vector, HashTable, etc., there will be invisible lock operations at this time. For example, the append() method of StringBuffer and the add() method of Vector:
public void vectorTest(){
Vector<String> vector = new Vector<String>();
for(int i = 0 ; i < 10 ; i++){
vector.add(i + "");
}
System.out.println(vector);
}
When running this code, the JVM can obviously detect that the variable vector does not escape from the method vectorTest(), so the JVM can boldly eliminate the lock operation inside the vector.
Lock coarsening
We know that when using the synchronization lock, we need to keep the scope of the synchronization block as small as possible - only in the actual scope of shared data, so as to minimize the number of operations that need to be synchronized. If there is lock competition, the thread waiting for the lock can also get the lock as soon as possible.
In most cases, the above view is correct, and LZ has always adhered to it. However, if a series of continuous lock and unlock operations, it may lead to unnecessary performance loss, so the concept of lock slang is introduced.
It's easy to understand the concept of lock swearing, which is to connect multiple consecutive lock and unlock operations and expand them into a larger range of locks. As shown in the above example: every time a vector is add ed, it needs to be locked. When the JVM detects that the same object is locked and unlocked continuously, it will merge a wider range of locking and unlocking operations, that is, the locking and unlocking operations will move out of the for loop.
Lightweight Locking
The main purpose of introducing lightweight lock is to reduce the performance consumption of the traditional heavyweight lock using the operating system mutual exclusion under the premise of multi thread competition. When the biased lock function is turned off or multiple threads compete for the biased lock and the biased lock is upgraded to a lightweight lock, an attempt will be made to acquire the lightweight lock. The steps are as follows:
Get lock
1. Judge whether the current object is in the unlocked state (hashcode, 0, 01). If so, the JVM will first create a space named Lock Record in the stack frame of the current thread, which is used to store the copy of the current Mark Word of the locked object (the copy is officially prefixed with a Displaced prefix, i.e. Displaced Mark Word); otherwise, execute step (3).
2. The JVM attempts to update the Mark Word of the object to point to the Lock Record by CAS operation. If the success indicates that the object competes for the lock, the lock flag bit will be changed to 00 (indicating that the object is in the light lock state), and the synchronization operation will be performed; if the failure, the step (3) will be performed.
3. Judge whether the Mark Word of the current object points to the stack frame of the current thread. If it is, it means that the current thread has held the lock of the current object, and the synchronization code block will be executed directly; otherwise, it only means that the lock object has been preempted by other threads. At this time, the lightweight lock needs to expand to the heavyweight lock, and the lock flag bit becomes 10, and the waiting thread will enter the blocking state.
Release lock
The release of lightweight lock is also carried out through CAS operation. The main steps are as follows:
1. Take out the data saved in the placed mark word when obtaining the lightweight lock;
2. Replace the extracted data in Mark Word of the current object with CAS operation. If it is successful, the lock is released successfully. Otherwise, execute (3).
3. If the CAS operation fails to replace, it means that other threads try to acquire the lock, they need to wake up the suspended thread while releasing the lock.
For lightweight locks, the basis for performance improvement is "for most locks, there will be no competition in the whole life cycle". If this basis is broken, there will be additional CAS operations in addition to the cost of mutual exclusion, so in the case of multi-threaded competition, lightweight locks are slower than heavyweight locks;
The following figure shows the acquisition and release process of lightweight locks
Bias lock
The main purpose of introducing biased lock is to minimize unnecessary lightweight lock execution path without multi thread competition. As mentioned above, lock and unlock operations of lightweight locks need to rely on multiple CAS atomic instructions. So how do biased locks reduce unnecessary CAS operations? We can see the structure of Mark work. Just check whether it is biased lock, lock ID and ThreadID. The processing flow is as follows:
Get lock
1. Check whether Mark Word is biased, i.e. whether it is biased lock 1 and lock identification bit is 01;
1. If it is biased, test whether the thread ID is the current thread ID; if it is, execute step (5); otherwise, execute step (3);
1. If the thread ID is not the current thread ID, the CAS operation will compete for the lock. If the competition is successful, the Mark Word's thread ID will be replaced with the current thread ID, otherwise the thread will be executed (4).
4. CAS fails to compete for lock, which proves that there is multi-threaded competition at present. When reaching the global security point, the thread obtaining the biased lock is suspended, the biased lock is upgraded to a lightweight lock, and then the thread blocked at the security point continues to execute the synchronization code block;
5. Execute synchronization code block
Release lock
The release of biased lock adopts a mechanism that only competition can release the lock. Threads do not actively release biased lock, and need to wait for other threads to compete. Revocation of biased locks requires waiting for a global security point (where there is no code executing on). The steps are as follows:
1. Suspend the thread with biased lock, and judge whether the lock object stone is still locked;
2. Remove the biased Su and restore to the unlocked state (01) or lightweight lock state;
The following figure shows the acquisition and release process of biased lock
Heavyweight lock
The heavyweight lock is implemented by the monitor inside the object. The essence of monitor depends on the Mutex Lock implementation of the underlying operating system. The switching between threads in the operating system requires the switching from user state to kernel state, and the switching cost is very high.
Reference material
Zhou Zhiming: deep understanding of Java virtual machine
Fang Tengfei: the art of Java Concurrent Programming
Implementation principle and application of synchronized in Java)