Synchronized is a keyword provided by the JVM, and Lock is essentially a class. Although both ensure thread synchronization, there are great differences in the specific implementation.
Related to Synchronized is the memory storage layout of objects. (Mark Word does not include the type pointer in the object header) as shown below:
Only the lock status field in the object header is closely related to Synchronized.
Flag bit and meaning of lock
Well, it sounds like you're bored and even want to give up. Let's do something interesting and take you deeper into Synchronized.
- Import jar package
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency>
- Writing test classes
package com.zhao.Synchinzed; import org.openjdk.jol.info.ClassLayout; public class A { public static void main(String[] args) throws InterruptedException { A a = new A(); ClassLayout classLayout = ClassLayout.parseInstance(a); synchronized (a){ System.out.println("At this time, it is unlocked and the console should print '01' "); } System.out.println(classLayout.toPrintable()); } }
- Console output results
Since the X86 architecture uses small end storage, we need to look backwards if we want to read the information in the object header!
Small end storage is to store the high bit to the high address and the low bit to the low address. Therefore, the corresponding information in the object header should be
At this time, the lock status is 001 and there is no lock status.
Bias lock
When I add a Synchronized synchronization code block to this class, and use this object as a lock. When there is only one thread, it will enter the biased lock, and the lock state will become 101.
Test class
package com.zhao.Synchinzed; import org.openjdk.jol.info.ClassLayout; import java.util.concurrent.TimeUnit; public class A { public static void main(String[] args) throws InterruptedException { TimeUnit.SECONDS.sleep(5); //A delay of at least 4 seconds is required to enter the bias lock A a = new A(); ClassLayout classLayout = ClassLayout.parseInstance(a); synchronized (a){ System.out.println("The bias lock console prints 101 and records thread information"); } System.out.println(classLayout.toPrintable()); } }
Console
Here, someone may ask why there are values on the hash code and some blank filling. I didn't call the hashcode() method to generate the hash code!
Fortunately, you didn't call it. If you call it, you will directly enter the heavyweight lock. What are these values? What is recorded here is the id and timestamp of the thread you obtained the bias lock.
Upgrade from lock free to biased lock process:
The first thread sees that the flag bit of the lock is 01 and the hash code is 0. It is the first thread to obtain the lock. It has the idea of taking it for itself. It records its thread id and timestamp information to the hash code area to complete the lock upgrade.
Lightweight Locking
When a second thread also wants to occupy the lock, the biased lock state will exit, and then upgrade to a lightweight lock. The thread that obtains the lock runs its own code fragment. If it does not obtain the lock, it keeps trying to obtain the lock by spinning.
Test class
package com.zhao.Synchinzed; import org.openjdk.jol.info.ClassLayout; import java.util.concurrent.TimeUnit; public class A { public static void main(String[] args) throws InterruptedException { TimeUnit.SECONDS.sleep(5); //A delay of at least 4 seconds is required to enter the bias lock A a = new A(); ClassLayout classLayout = ClassLayout.parseInstance(a); synchronized (a){ System.out.println("The bias lock console prints 101 and records thread information"); } System.out.println(classLayout.toPrintable()); new Thread((() -> { synchronized (a){ try { TimeUnit.SECONDS.sleep(1); //You need to sleep for 1 second to make the lock compete. When the second thread sees that the lock is occupied, it will immediately upgrade the lock to a lightweight lock System.out.println("My thread"+Thread.currentThread().getName()+" I'm here to compete for the lock"); } catch (Exception e) { } } }),"A").start(); System.out.println(classLayout.toPrintable()); } }
Console
In the unlocked state, each thread stack frame will have a copy of Mark Word called Displaced Mark Word. Stored in an area called Lock Record. Each thread wants to write its own Lock Record pointer to the object's Mark Word (this paragraph can't be understood, turn up and down: lock flag bit and meaning). If this thread uses cas successfully, it writes its own Lock Record pointer to the object's Mark Word. At this time, it will change the object's Mark Word lock flag bit to 00, It also stores information such as Mark Word, hashcode and the age of the object in the object header in the heap
The process of upgrading deflection lock to lightweight lock:
The second thread attempts to acquire a lock and writes its Lock Record pointer to make word. Once it obtains the lock, it will write its own Lock Record pointer and modify the flag bit of the lock to 00. There is a CAS process during this process. If the lock flag bit of Mark Word in the heap memory is changed to 00, it means that the CAS has succeeded this time. If the lock flag bit has been changed to 00, it means that the CAS has failed this time.
Heavyweight lock
If two or more threads compete for a lock, it will expand into a heavyweight lock, and the flag bit of the lock will become 10. In the heap memory object header, Mark Word stores the pointer of the Monitor lock and the flag bit 10 of the lock.
Test class
package com.zhao.Synchinzed; import org.openjdk.jol.info.ClassLayout; import java.util.concurrent.TimeUnit; public class A { public static void main(String[] args) throws InterruptedException { TimeUnit.SECONDS.sleep(5); //A delay of at least 4 seconds is required to enter the bias lock A a = new A(); ClassLayout classLayout = ClassLayout.parseInstance(a); synchronized (a) { System.out.println("The bias lock console prints 101 and records thread information"); } System.out.println(classLayout.toPrintable()); new Thread((() -> { synchronized (a) { try { TimeUnit.SECONDS.sleep(1); //You need to sleep for 1 second to make the lock compete. The second thread sees that the lock is occupied and immediately upgrades the lock to a lightweight lock System.out.println("My thread" + Thread.currentThread().getName() + " I'm here to compete for the lock"); } catch (Exception e) { } } }), "A").start(); System.out.println(classLayout.toPrintable()); new Thread((() -> { synchronized (a) { System.out.println("My thread" + Thread.currentThread().getName() + " I'm here to compete for the lock"); } }), "B").start(); System.out.println(classLayout.toPrintable()); } }
Console
Upgrade from lightweight lock to heavyweight lock:
More and more threads use CAS to try to write their Lock Record pointer to Mark Word. However, one thread has not obtained the lock using CAS. So he did useless work, hoping to get the lock next time. In this way, the CPU will soar quickly because the thread does not get the lock and keeps spinning! Therefore, there is the heavyweight lock below. More than two threads compete for the same lock, and the flag bit of the lock will become "10". What is stored in Mark Word is the monitor pointer and the flag bit of the lock 10.
Synchronization method and bottom implementation of synchronization code block
The code modified by the synchronous code block will add two instructions before and after the bytecode compiled by the compiler, one monitorenter, a monitorexit, as shown in the following figure:
0 aload_0 1 dup 2 astore_1 3 monitorenter 4 getstatic #9 <java/lang/System.out : Ljava/io/PrintStream;> 7 new #20 <java/lang/StringBuilder> 10 dup 11 invokespecial #21 <java/lang/StringBuilder.<init> : ()V> 14 ldc #22 < my thread > 16 invokevirtual #23 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;> 19 invokestatic #24 <java/lang/Thread.currentThread : ()Ljava/lang/Thread;> 22 invokevirtual #25 <java/lang/Thread.getName : ()Ljava/lang/String;> 25 invokevirtual #23 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;> 28 ldc #26 < here to compete for locks > 30 invokevirtual #23 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;> 33 invokevirtual #27 <java/lang/StringBuilder.toString : ()Ljava/lang/String;> 36 invokevirtual #11 <java/io/PrintStream.println : (Ljava/lang/String;)V> 39 aload_1 40 monitorexit 41 goto 49 (+8) 44 astore_2 45 aload_1 46 monitorexit 47 aload_2 48 athrow 49 return
There are two monitorexits on the top because your code has try...catch... To run the monitorexit on the top normally. If an exception occurs, go to the monitorexit on the bottom.
A thread comes to get the lock. See The monitorenter instruction will be associated with a monitor object, the monitor record will be incremented by 1, and the monitor pointer will be written to the object header. The flag bit of the modified lock is' 10 ',
This process is reentrant, that is, you call the Synchronized method in the Synchronized method is allowed, you only need to add the monitor counter to 1. When other threads come, they will check that the monitor counter is not 0, they will know that a thread has obtained the lock, and it will wait. When the thread obtaining the lock releases the lock, the monitor counter will be released several times after it is increased several times. Clear the counter to complete the release of the lock! Let other threads continue to compete for the lock.
Synchronization method
public synchronized void SS() { System.out.println("Synchronization method"); }
public synchronized void SS(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=2, locals=1, args_size=1 0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #7 / / String synchronization method 5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 14: 0 line 15: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 this Lcom/zhao/Synchinzed/B; }
When the JVM detects' ACC '_ Synchronized 'will be called implicitly after this flag bit Monitorenter, and monitorexit. Add monitorenter before calling the method. After executing the synchronization method, monitorexit will be called.
Volatile keyword and thread
If you want to make Volatile clear, you must first talk about the computer model. However, only three things are involved here: CPU, main memory and hard disk. Their three speeds, CPU is like an airplane, and main memory is like a car. Hard drives are like bicycles. In the computer, the interaction between the CPU and the main memory is common, but their speed difference is too much. It takes 100s to read data from the memory, and the CPU finishes processing in one second. In the remaining 99 seconds, the CPU is idle there.
Based on the principle that idleness is waste, a lot of cache is added at the CPU level to solve the problem of slow reading. When data is read from memory for the first time, the data is placed in the cache. The next time you read the same data, you don't need to read it from memory.
Our threads are similar. They read data from the main memory to their own working memory. When they read the data in the working memory directly the next time, they don't need to interact with the heap memory frequently.
But it's easy