Use of built-in lock
Generally, the java built-in lock we mentioned refers to the lock implemented by the synchronized keyword provided by the JVM. Here is a simple example:
public class SynchronizedVariableTest1 { public static void main(String[] args) throws InterruptedException { SynchronizedVariableTest1 test = new SynchronizedVariableTest1(); synchronized (test) { System.out.println(1); } } } Copy code
Object locking
We can view bytecode files through javap commands, or view bytecode instruction information through jclasslib Bytecode Viewer plug-in of idea, as shown in the following figure:
We can see that the bottom layer of synchronized uses the monitor mechanism to obtain and release locks. The monitorenter and monitorexit instructions will be added before and after the code is fast.
**The locked object cannot be null. If it is null, the program will prompt * * * * NullPointerException * * null pointer exception when running.
Object lock = null; synchronized(lock) { System.out.println(100); } // result: // Exception in thread "main" java.lang.NullPointerException // at cn.xyz.juc.synchronized1.status.CleanLockTest.main(CleanLockTest.java:16) Copy code
Method locking
Similarly, if the synchronized keyword is added to the method (because it is not convenient for the jclasslib Bytecode Viewer to view the method information, I will demonstrate it through the javap -verbose instruction below), ACC will be added to the flags of the method_ Synchronized keyword. The original method code is as follows:
public synchronized void test() { } Copy code
The compiled bytecode executes the instruction javap - verbose XXXX class
public synchronized void test(); descriptor: ()V flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=2, locals=1, args_size=1 0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 3: iconst_1 4: invokevirtual #6 // Method java/io/PrintStream.println:(I)V line 14: 21 LocalVariableTable: Start Length Slot Name Signature 0 22 0 this Lcn/xyz/juc/synchronized1/SynchronizedVariableTest2; Copy code
For For parsing and analysis of class bytecode files, please refer to this Nuggets article: JVM bytecode instruction parsing .
Built in lock status storage
The state of the built-in lock is stored in the object header of the Java object. For the storage structure of the object header and the object memory allocation, please refer to my article: Uncover the secrets of Java objects based on Hostpot virtual machine . This article will not repeat.
Mark Word (64bit)
In order to facilitate the following reading, I will paste mark word into this article again
Java tube pass
Java virtual machine can support method level synchronization and synchronization of an instruction sequence within a method. Both synchronization structures are implemented by Monitor (more commonly referred to as "lock").
java adopts the management process technology. The synchronized keyword and the three methods of wait(), notify(), and notifyAll() are all components of the management process. The pipe pass and semaphore are equivalent. The so-called equivalence refers to that the semaphore can be realized by the pipe pass, and the pipe pass can also be realized by the semaphore. However, the management process uses the encapsulation characteristics of OOP to solve the complexity of semaphores in engineering practice, so java adopts the management mechanism.
MESA model
Pipe pass model: Hassen model, Hoare model and MESA model. Among them, the MESA model is widely used now, and the implementation of Java management also refers to the MESA model.
In the field of concurrent programming, there are two core problems: one is mutual exclusion, that is, only one thread is allowed to access shared resources at the same time; The other is synchronization, that is, how threads communicate and cooperate. These two problems can be solved by the tube side.
ObjectMonitor
The definition information of ObjectMonitor in the jvm is as follows:
// initialize the monitor, exception the semaphore, all other fields // are simple integers or pointers ObjectMonitor() { _header = NULL; // Object header _count = 0; _waiters = 0, _recursions = 0; // Lock reentry times _object = NULL; // Store lock object _owner = NULL; // Identifies the thread that owns the monitor (the thread that currently acquires the lock) _WaitSet = NULL; // A two-way circular linked list composed of waiting threads (calling waite)_ WaitSet is the first node _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; // The multi-threaded contention lock will be stored in the one-way linked list (FIFO) structure first FreeNext = NULL ; // Store threads that are Blocked when entering or re entering (i.e. threads that fail to compete) _EntryList = NULL ; _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0; } Copy code
Built in lock status
The built-in lock is divided into four states: no lock, bias lock, lightweight lock and heavyweight lock. In the process of lock competition, a positive lock upgrade process will be carried out.
Lock upgrade process
Note: the lock upgrade status is irreversible.
Unlocked state
Experiment code:
public class NoSynchronizedTest { public static void main(String[] args) { NoSynchronizedTest test = new NoSynchronizedTest(); System.out.println("Unlocked state +++++++++"); System.out.println(ClassLayout.parseInstance(test).toPrintable()); } } Copy code
Output results
Unlocked state +++++++++ cn.xyz.juc.synchronized1.NoSynchronizedTest object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 05 c0 00 f8 (00000101 11000000 00000000 11111000) (-134168571) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total Copy code
Field interpretation:
- OFFSET: address OFFSET, in bytes;
- SIZE: occupied memory SIZE, in bytes;
- TYPE DESCRIPTION: TYPE DESCRIPTION, where object header is the object header;
- VALUE: corresponding to the VALUE currently stored in memory, binary 32;
Pointer compression
As a result of printing, we can see that the total size of the object is 16 bytes. The first 12 bytes are the object header (pointer compression is enabled by default in my local jdk 1.8), and the last 4 bytes are aligned filling.
It can be closed through the following parameters:
-XX:-UseCompressedOops Copy code
I'll run it again. The total size of the object is 16 bytes. The first 8 bytes are mark word, and the last 8 bytes represent kclass point
Anonymity bias
When the JVM enables the skew lock mode (JDK 6 is enabled by default), the Thread Id of the new Mark Word created is 0, indicating that it is in a skewable but not biased to any thread, which is also called anonymous biased
Bias lock state
Bias lock delay bias
**There is a bias lock delay mechanism in the bias lock mode: * * after the Hostpost virtual machine is restarted, there is a delay of a few seconds (4 seconds by default) before starting the bias lock mode for new objects. When the JVM starts, a series of object creation processes are performed. In this process, a large number of synchronized keywords lock objects. Most of these locks are not biased locks. To reduce initialization time, the JVM delays loading biased locks by default.
JVM parameters:
//Closing delay opening bias lock ‐XX:BiasedLockingStartupDelay=0 //No deflection lock ‐XX:‐UseBiasedLocking //Enable deflection lock ‐XX:+UseBiasedLocking Copy code
Test code (note that it should be noted here that two objects need to be created, because the initialization of object header information is initialized when the new keyword is executed. The author has encountered such a problem before, resulting in the failure of the experiment):
public class BiasedLockDelayTest { public static void main(String[] args) throws InterruptedException { Object obj1 = new Object(); System.out.println(ClassLayout.parseInstance(obj1).toPrintable()); System.out.println(); Thread.sleep(5000); Object obj2 = new Object(); System.out.println(ClassLayout.parseInstance(obj2).toPrintable()); } } Copy code
The results are printed as follows. After a delay of 5 seconds, the anonymous bias is turned on by default, and threadid = 0.
Bias lock bias state tracking
The following code mainly demonstrates the process of an object from no lock to biased lock. The code is as follows:
log.debug(ClassLayout.parseInstance(new Object()).toPrintable()); Thread.sleep(4000); Object obj1 = new Object(); log.debug(ClassLayout.parseInstance(obj1).toPrintable()); new Thread(new Runnable() { @Override public void run() { log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); synchronized (obj1) { log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } }, "thread0").start(); Thread.sleep(5000); log.debug(ClassLayout.parseInstance(obj1).toPrintable()); Copy code
The printing results are as follows:
The hashcode method is called when the lock state is biased
Call obj. Of lock pair redemption Hashcode () or system When using the identityhashcode (obj) method, the bias lock of the object will be revoked. Because an object's hashcode will only be generated once and saved, there is no place to store the hashcode
- Lightweight locks record hashCode in the lock record
- The heavyweight lock records the hashCode in the Monitor
When the object is biased (that is, the thread ID is 0) and biased, calling hashCode calculation will make the object unable to be biased:
- When the object can be biased, MarkWord will program an unlocked state and can only be upgraded to a lightweight lock
- When the object is in a biased lock, call hashCode to force the biased lock to be upgraded to a heavyweight lock
Experiment code:
The hashCode method of the shared object obj1 is called inside the lock, and the lock is upgraded to a heavyweight lock.
Call wait/notify in the lock state
Execute obj in the bias lock state Notify will be upgraded to a lightweight lock.
Call obj Wait (timeout) will be upgraded to heavyweight lock.
Lightweight lock status
If the bias lock fails, the virtual machine will not be upgraded to a heavyweight lock. It will also try to use an optimization method called lightweight lock. At this time, the structure of Mark Word will also become a lightweight lock. The scenario of lightweight lock is that threads alternately execute synchronization blocks. If there are occasions where multiple threads access the same lock at the same time, This will cause the lightweight lock to expand into a heavyweight lock.
Upgrade bias lock to lightweight lock
@Slf4j public class LightweightLockTest { public static void main(String[] args) throws InterruptedException { log.debug(ClassLayout.parseInstance(new Object()).toPrintable()); Thread.sleep(5000); Object obj1 = new Object(); log.debug(ClassLayout.parseInstance(obj1).toPrintable()); new Thread(new Runnable() { @SneakyThrows @Override public void run() { log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); synchronized (obj1) { log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } }, "thread0").start(); Thread.sleep(1000); new Thread(new Runnable() { @SneakyThrows @Override public void run() { log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); synchronized (obj1) { log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } }, "thread1").start(); Thread.sleep(5000); log.debug(ClassLayout.parseInstance(obj1).toPrintable()); } } Copy code
Print log:
Because the log is too long to be marked, I'll simply draw a flow chart
Upgrade from lightweight lock to heavyweight lock
To create a highly competitive scenario, we can simulate it through thread pool. The code is as follows:
@Slf4j public class LightweightLockTest { public static void main(String[] args) throws InterruptedException { log.debug(ClassLayout.parseInstance(new Object()).toPrintable()); Thread.sleep(5000); Object obj1 = new Object(); log.debug(ClassLayout.parseInstance(obj1).toPrintable()); ExecutorService executeService = Executors.newFixedThreadPool(2); executeService.submit(new Thread(new Runnable() { @SneakyThrows @Override public void run() { log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); synchronized (obj1) { log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } }, "thread0")); //Thread.sleep(1000); executeService.submit(new Thread(new Runnable() { @SneakyThrows @Override public void run() { log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); synchronized (obj1) { log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable()); } }, "thread1")); Thread.sleep(5000); log.debug(ClassLayout.parseInstance(obj1).toPrintable()); } } Copy code
Let's look at the result again: after the first thread obtains the lock, the lock is upgraded from biased lock to lightweight lock
The second thread has adopted heavyweight locks. Note: why not upgrade to heavyweight locks sometimes? This may be because the CPU resources are relatively idle, the computing logic processing capacity is relatively strong, and there is no need to upgrade the lock. You can try to add several more threads to compete for the lock, or run the program more times.
Lock state transition
The state transformation of bias lock and lightweight lock and the relationship transformation of object Mark Word are shown in the following figure:
Original link: https://juejin.cn/post/7055665818176061453