1. introduction
Thread safety is the top priority of concurrent programming, and there are two main causes of concurrent problems.
1. Critical resources exist
2. Multiple threads operate on shared data at the same time
Based on this situation, we will consider a mechanism that when one thread accesses critical resources, other threads cannot access them. This mechanism is called mutex. Synchronized in java is such a mechanism that only one thread can access the decorated code block at the same time, thus ensuring the security. Synchronized also ensures that the changes of one thread (mainly the changes of shared data) are seen by other threads, that is to say, variables in the synchronized code block do not need to use the volatile keyword.
2. Three ways to use synchronized
Modify the instance method to lock the current instance and obtain the lock of the current instance before entering the synchronization code
Modify the static method to lock the current class object, and obtain the lock of the current class object before entering the synchronization code
Decorate the code block, specify the lock object, lock the given object, and obtain the lock of the given object before entering the synchronization code base.
package com.hqa.design.test; import java.util.concurrent.CountDownLatch; public class Test { /** * An instance lock */ private static final Object lock = new Object(); /** * Static method lock, acting on Test.class object */ public static synchronized void classLock() { try { System.out.println(Thread.currentThread().getName() + "Get into classLock Method,Dormancy 5 s"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + "Sign out classLock Method"); } catch (InterruptedException e) { e.printStackTrace(); } } /** * Object instance lock, acting on the current Test class instance */ public synchronized void thisLock() { try { System.out.println(Thread.currentThread().getName() + "Get into thisLock Method,Dormancy 5 s"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + "Sign out thisLock Method"); } catch (InterruptedException e) { e.printStackTrace(); } } /** * Lock object is lock */ public void objLock() { synchronized (lock) { try { System.out.println(Thread.currentThread().getName() + "Get into objLock Method,Dormancy 5 s"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + "Sign out objLock Method"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { //Declare a lock. The function of this class will not be discussed now. You only need to know this class here //It is to let the main thread continue to execute after all the sub threads have finished executing final CountDownLatch cnt = new CountDownLatch(2); // classLock test Thread t1 = new Thread(() -> { Test.classLock(); cnt.countDown(); }); Thread t2 = new Thread(() -> { Test.classLock(); cnt.countDown(); }); t1.start(); t2.start(); cnt.await(); System.out.println("===============classLock Test Over=============="); CountDownLatch cnt2 = new CountDownLatch(2); // thisLock test Test t = new Test(); Thread t3 = new Thread(() -> { t.thisLock(); cnt2.countDown(); }); Thread t4 = new Thread(() -> { t.thisLock(); cnt2.countDown(); }); t3.start(); t4.start(); cnt2.await(); System.out.println("===============thisLock Test Over=============="); CountDownLatch cnt3 = new CountDownLatch(2); // Obklock test Thread t5 = new Thread(() -> { t.objLock(); cnt3.countDown(); }); Thread t6 = new Thread(() -> { t.objLock(); cnt3.countDown(); }); t5.start(); t6.start(); cnt3.await(); System.out.println("===============objLock Test Over=============="); } }
Output:
Thread-0 enters the classLock method and sleeps for 5s Thread-0 exits classLock method Thread-1 enters the classLock method and sleeps for 5s Thread-1 exits classLock method ===============classLock Test Over============== Thread-3 enters the thisLock method, sleeping for 5s Thread-3 exits the thisLock method Thread-2 enters thisLock method, and sleeps for 5s Thread-2 exits the thisLock method ===============thisLock Test Over============== Thread-4 enters obklock method and sleeps for 5s Thread-4 exits objLock method Thread-5 enters obklock method and sleeps for 5s Thread-5 exits objLock method ===============objLock Test Over==============
3.synchronized semantics
The synchronization in the JVM virtual machine is based on the Monitor object, whether it is shown or implied here. We need to note that the synchronization method is not implemented by monitorenter and monitorexit, but by the instruction reading the ACC ﹣ synchronized flag of the method in the runtime constant pool.
4.Java object header
In the JVM, the layout of objects in memory is divided into three parts: object header, instance data, and alignment fill. As follows:
ps: picture from: https://blog.csdn.net/javazejian/article/details/72828483
There are two types of object headers.
4.1 common object head
1,Mark Word
2. Pointer to class
4.2 array object header
1,Mark Word
2. Pointer to class
3. Array length (only for array objects)
4.3 Mark Word related to synchronization lock
Mark Word records information about an object and a lock. When the object is treated as a synchronized lock by the synchronized keyword, a series of operations around the lock are related to Mark Word.
Mark Word is 32bit in 32-bit JVM and 64bit in 64 bit JVM.
Mark Word stores different contents in different lock states. This is how it is stored in a 32-bit JVM:
Lock state |
25bit |
4bit |
1bit |
2bit |
|
23bit |
2bit |
Is it biased to lock |
Lock flag bit |
||
No lock |
Object's HashCode |
Age of generational age |
0 |
01 |
|
Bias lock |
Thread ID |
Epoch |
Age of generational age |
1 |
01 |
Lightweight Locking |
Pointer to the lock record in the stack |
00 |
|||
Heavyweight lock |
Pointer to heavyweight lock |
10 |
|||
GC marker |
empty |
11 |
After JDK 1.6, there is a concept of lock upgrade when dealing with synchronous locks. The JVM starts to deal with synchronous locks from biased locks. With the increasingly fierce competition, the processing mode is upgraded from biased locks to lightweight locks, and finally to heavyweight locks.
In general, a JVM uses locks and Mark Word as follows:
1. When it is not treated as a lock, this is a common object. Mark Word records the HashCode of the object. The lock flag bit is 01, and whether it is biased to the lock bit is 0.
2. When an object is treated as A synchronous lock and A thread A grabs the lock, the lock flag bit is still 01, but whether it is biased to the lock bit is changed to 1. The first 23 bits record the thread id of the seized lock, indicating that it enters the biased lock state.
3. When thread A tries to acquire the lock again, the JVM finds that the flag bit of the synchronization lock object is 01, and whether the bias lock is 1, that is, the bias state. The thread id recorded in Mark Word is thread A's own id, indicating that thread A has acquired the bias lock and can execute the code of the synchronization lock.
4. When thread B attempts to acquire the lock, the JVM finds that the synchronization lock is in A biased state, but the thread id in Mark Word does not record B, then thread B will first attempt to acquire the lock with CAS operation, which is likely to succeed, because thread A generally does not automatically release the biased lock. If the lock grabbing is successful, change the thread id in Mark Word to the id of thread B, which means that thread B has obtained the biased lock and can execute the synchronous lock code. If the lock grabbing fails, continue with step 5.
5. Failure to grab lock in biased lock state indicates that there is a certain competition between current locks, and biased locks will be upgraded to lightweight locks. The JVM will open a separate space in the thread stack of the current thread, in which the pointer to the object lock Mark Word is saved, and the pointer to this space is saved in the object lock Mark Word. The above two save operations are CAS operations. If the save is successful, it means that the thread has seized the synchronization lock. Change the lock flag bit in Mark Word to 00 to execute the synchronization lock code. If the saving fails, it means that the lock grabbing fails. The competition is too fierce. Continue to step 6.
6. If the lightweight lock fails to grab the lock, the JVM will use the spin lock. The spin lock is not a lock state, but represents a continuous retry, trying to grab the lock. Starting from JDK 1.7, spin lock is enabled by default, and the number of spins is determined by the JVM. If the lock grabbing is successful, execute the synchronization lock code. If it fails, continue to step 7.
7. If the lock still fails after the spin lock retries, the synchronization lock will be upgraded to the heavyweight lock, and the lock flag bit will be changed to 10. In this state, threads that do not grab the lock will be blocked.
5. Heavyweight lock
We mainly analyze the heavyweight lock, that is, the synchronized object lock. The lock identification bit is 10, where the pointer points to the starting address of the monitor object (also known as the pipe or monitor lock). Each object has a monitor associated with it. There are many ways to realize the relationship between the object and its monitor. For example, monitor can be created and destroyed together with the object or generated automatically when the thread tries to acquire the object lock, but when a monitor is held by a thread, it is in the locked state. In the Java virtual machine (HotSpot), monitor is implemented by ObjectMonitor. Its main data structure is as follows (located in the source code ObjectMonitor.hpp file of HotSpot virtual machine, implemented in C + +)
ObjectMonitor() { _header = NULL; _count = 0; //Number of records _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; //Threads in the wait state will be added to the WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; //Threads waiting for lock block will be added to the list _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; }
There are two queues in ObjectMonitor, i.e. "WaitSet" and "EntryList", which are used to save ObjectWaiter object list (each thread waiting for lock will be encapsulated as ObjectWaiter object), "owner" refers to the thread holding ObjectMonitor object. When multiple threads access a synchronous code at the same time, they will first enter the "EntryList" collection, and enter "owner" when the thread obtains the monitor of the object Area and set the owner variable in the monitor to the current thread and add 1 to the counter count in the monitor. If the thread calls the wait() method, the currently held monitor will be released. The owner variable will return to null, and the count will decrease by 1. At the same time, the thread will enter the WaitSet collection and wait to be awakened. If the current thread finishes executing, the monitor will also be released and the value of the variable will be reset so that other threads can get the monitor. As shown in the figure below
The monitor Object exists in the Object header of each Java Object (the point of stored pointer). The synchronized lock obtains the lock in this way, which is also the reason why any Object in Java can be used as a lock, and the reason why notify/notifyAll/wait and other methods exist in the top Object
6.synchronized Decompilation
6.1 prepare a class first
package com.hqa.design.test; import java.util.concurrent.CountDownLatch; public class Test { /** * An instance lock */ private static final Object lock = new Object(); /** * Static method lock, acting on Test.class object */ public static synchronized void classLock() { try { System.out.println(Thread.currentThread().getName() + "Get into classLock Method,Dormancy 5 s"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + "Sign out classLock Method"); } catch (InterruptedException e) { e.printStackTrace(); } } /** * Object instance lock, acting on the current Test class instance */ public void thisLock() { synchronized (this) { try { System.out.println(Thread.currentThread().getName() + "Get into thisLock Method,Dormancy 5 s"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + "Sign out thisLock Method"); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * Lock object is lock */ public void objLock() { synchronized (lock) { try { System.out.println(Thread.currentThread().getName() + "Get into objLock Method,Dormancy 5 s"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + "Sign out objLock Method"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
6.2 view the javap of Test.class
javap -c -l -v Test > out.txt
Output:
Classfile /D:/mcWork/workspace/design-share/bin/com/hqa/design/test/Test.class Last modified 2020-1-20; size 1837 bytes MD5 checksum eaeadfde841431d8d72d966bc14159ba Compiled from "Test.java" public class com.hqa.design.test.Test minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Class #2 // com/hqa/design/test/Test #2 = Utf8 com/hqa/design/test/Test #3 = Class #4 // java/lang/Object #4 = Utf8 java/lang/Object #5 = Utf8 lock #6 = Utf8 Ljava/lang/Object; #7 = Utf8 <clinit> #8 = Utf8 ()V #9 = Utf8 Code #10 = Methodref #3.#11 // java/lang/Object."<init>":()V #11 = NameAndType #12:#8 // "<init>":()V #12 = Utf8 <init> #13 = Fieldref #1.#14 // com/hqa/design/test/Test.lock:Ljava/lang/Object; #14 = NameAndType #5:#6 // lock:Ljava/lang/Object; #15 = Utf8 LineNumberTable #16 = Utf8 LocalVariableTable #17 = Utf8 this #18 = Utf8 Lcom/hqa/design/test/Test; #19 = Utf8 classLock #20 = Fieldref #21.#23 // java/lang/System.out:Ljava/io/PrintStream; #21 = Class #22 // java/lang/System #22 = Utf8 java/lang/System #23 = NameAndType #24:#25 // out:Ljava/io/PrintStream; #24 = Utf8 out #25 = Utf8 Ljava/io/PrintStream; #26 = Class #27 // java/lang/StringBuilder #27 = Utf8 java/lang/StringBuilder #28 = Methodref #29.#31 // java/lang/Thread.currentThread:()Ljava/lang/Thread; #29 = Class #30 // java/lang/Thread #30 = Utf8 java/lang/Thread #31 = NameAndType #32:#33 // currentThread:()Ljava/lang/Thread; #32 = Utf8 currentThread #33 = Utf8 ()Ljava/lang/Thread; #34 = Methodref #29.#35 // java/lang/Thread.getName:()Ljava/lang/String; #35 = NameAndType #36:#37 // getName:()Ljava/lang/String; #36 = Utf8 getName #37 = Utf8 ()Ljava/lang/String; #38 = Methodref #39.#41 // java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; #39 = Class #40 // java/lang/String #40 = Utf8 java/lang/String #41 = NameAndType #42:#43 // valueOf:(Ljava/lang/Object;)Ljava/lang/String; #42 = Utf8 valueOf #43 = Utf8 (Ljava/lang/Object;)Ljava/lang/String; #44 = Methodref #26.#45 // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V #45 = NameAndType #12:#46 // "<init>":(Ljava/lang/String;)V #46 = Utf8 (Ljava/lang/String;)V #47 = String #48 / / enter classLock method and sleep for 5s #48 = Utf8 enters classLock method, sleep for 5s #49 = Methodref #26.#50 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #50 = NameAndType #51:#52 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #51 = Utf8 append #52 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #53 = Methodref #26.#54 // java/lang/StringBuilder.toString:()Ljava/lang/String; #54 = NameAndType #55:#37 // toString:()Ljava/lang/String; #55 = Utf8 toString #56 = Methodref #57.#59 // java/io/PrintStream.println:(Ljava/lang/String;)V #57 = Class #58 // java/io/PrintStream #58 = Utf8 java/io/PrintStream #59 = NameAndType #60:#46 // println:(Ljava/lang/String;)V #60 = Utf8 println #61 = Long 5000l #63 = Methodref #29.#64 // java/lang/Thread.sleep:(J)V #64 = NameAndType #65:#66 // sleep:(J)V #65 = Utf8 sleep #66 = Utf8 (J)V #67 = String #68 / / exit classLock method #68 = Utf8 exits classLock method #69 = Methodref #70.#72 // java/lang/InterruptedException.printStackTrace:()V #70 = Class #71 // java/lang/InterruptedException #71 = Utf8 java/lang/InterruptedException #72 = NameAndType #73:#8 // printStackTrace:()V #73 = Utf8 printStackTrace #74 = Utf8 e #75 = Utf8 Ljava/lang/InterruptedException; #76 = Utf8 StackMapTable #77 = Utf8 thisLock #78 = String #79 / / enter thisLock method and sleep for 5s #79 = Utf8 enters thisLock method, sleep for 5s #80 = String #81 / / exit thisLock method #81 = Utf8 exits thisLock method #82 = Utf8 objLock #83 = String #84 / / enter obklock method and sleep for 5s #84 = Utf8 enters obklock method and sleeps for 5s #85 = String #86 / / exit objLock method #86 = Utf8 exits obklock method #87 = Class #88 // java/lang/Throwable #88 = Utf8 java/lang/Throwable #89 = Utf8 SourceFile #90 = Utf8 Test.java { static {}; descriptor: ()V flags: ACC_STATIC Code: stack=2, locals=0, args_size=0 0: new #3 // class java/lang/Object 3: dup 4: invokespecial #10 // Method java/lang/Object."<init>":()V 7: putstatic #13 // Field lock:Ljava/lang/Object; 10: return LineNumberTable: line 10: 0 LocalVariableTable: Start Length Slot Name Signature public com.hqa.design.test.Test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #10 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 5: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/hqa/design/test/Test; public static synchronized void classLock(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED Code: stack=4, locals=1, args_size=0 0: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #26 // class java/lang/StringBuilder 6: dup 7: invokestatic #28 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 10: invokevirtual #34 // Method java/lang/Thread.getName:()Ljava/lang/String; 13: invokestatic #38 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 16: invokespecial #44 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 19: ldc #47 / / String enter classLock method, sleep for 5s 21: invokevirtual #49 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: invokevirtual #53 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 27: invokevirtual #56 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: ldc2_w #61 // long 5000l 33: invokestatic #63 // Method java/lang/Thread.sleep:(J)V 36: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 39: new #26 // class java/lang/StringBuilder 42: dup 43: invokestatic #28 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 46: invokevirtual #34 // Method java/lang/Thread.getName:()Ljava/lang/String; 49: invokestatic #38 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 52: invokespecial #44 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 55: ldc #67 / / String exit classLock method 57: invokevirtual #49 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 60: invokevirtual #53 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 63: invokevirtual #56 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 66: goto 74 69: astore_0 70: aload_0 71: invokevirtual #69 // Method java/lang/InterruptedException.printStackTrace:()V 74: return Exception table: from to target type 0 66 69 Class java/lang/InterruptedException LineNumberTable: line 17: 0 line 18: 30 line 19: 36 line 20: 66 line 21: 70 line 23: 74 LocalVariableTable: Start Length Slot Name Signature 70 4 0 e Ljava/lang/InterruptedException; StackMapTable: number_of_entries = 2 frame_type = 247 /* same_locals_1_stack_item_frame_extended */ offset_delta = 69 stack = [ class java/lang/InterruptedException ] frame_type = 4 /* same */ public synchronized void thisLock(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=4, locals=2, args_size=1 0: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #26 // class java/lang/StringBuilder 6: dup 7: invokestatic #28 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 10: invokevirtual #34 // Method java/lang/Thread.getName:()Ljava/lang/String; 13: invokestatic #38 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 16: invokespecial #44 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 19: ldc #78 / / String enter thisLock method, sleep for 5s 21: invokevirtual #49 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: invokevirtual #53 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 27: invokevirtual #56 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: ldc2_w #61 // long 5000l 33: invokestatic #63 // Method java/lang/Thread.sleep:(J)V 36: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 39: new #26 // class java/lang/StringBuilder 42: dup 43: invokestatic #28 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 46: invokevirtual #34 // Method java/lang/Thread.getName:()Ljava/lang/String; 49: invokestatic #38 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 52: invokespecial #44 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 55: ldc #80 / / String exit thisLock method 57: invokevirtual #49 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 60: invokevirtual #53 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 63: invokevirtual #56 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 66: goto 74 69: astore_1 70: aload_1 71: invokevirtual #69 // Method java/lang/InterruptedException.printStackTrace:()V 74: return Exception table: from to target type 0 66 69 Class java/lang/InterruptedException LineNumberTable: line 30: 0 line 31: 30 line 32: 36 line 33: 66 line 34: 70 line 36: 74 LocalVariableTable: Start Length Slot Name Signature 0 75 0 this Lcom/hqa/design/test/Test; 70 4 1 e Ljava/lang/InterruptedException; StackMapTable: number_of_entries = 2 frame_type = 247 /* same_locals_1_stack_item_frame_extended */ offset_delta = 69 stack = [ class java/lang/InterruptedException ] frame_type = 4 /* same */ public void objLock(); descriptor: ()V flags: ACC_PUBLIC Code: stack=4, locals=3, args_size=1 0: getstatic #13 // Field lock:Ljava/lang/Object; 3: dup 4: astore_1 5: monitorenter 6: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 9: new #26 // class java/lang/StringBuilder 12: dup 13: invokestatic #28 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 16: invokevirtual #34 // Method java/lang/Thread.getName:()Ljava/lang/String; 19: invokestatic #38 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 22: invokespecial #44 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 25: ldc #83 / / String enter obklock method, sleep for 5s 27: invokevirtual #49 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 30: invokevirtual #53 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 33: invokevirtual #56 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 36: ldc2_w #61 // long 5000l 39: invokestatic #63 // Method java/lang/Thread.sleep:(J)V 42: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 45: new #26 // class java/lang/StringBuilder 48: dup 49: invokestatic #28 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 52: invokevirtual #34 // Method java/lang/Thread.getName:()Ljava/lang/String; 55: invokestatic #38 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 58: invokespecial #44 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 61: ldc #85 / / String exit objLock method 63: invokevirtual #49 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 66: invokevirtual #53 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 69: invokevirtual #56 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 72: goto 80 75: astore_2 76: aload_2 77: invokevirtual #69 // Method java/lang/InterruptedException.printStackTrace:()V 80: aload_1 81: monitorexit 82: goto 88 85: aload_1 86: monitorexit 87: athrow 88: return Exception table: from to target type 6 72 75 Class java/lang/InterruptedException 6 82 85 any 85 87 85 any LineNumberTable: line 42: 0 line 44: 6 line 45: 36 line 46: 42 line 47: 72 line 48: 76 line 42: 80 line 51: 88 LocalVariableTable: Start Length Slot Name Signature 0 89 0 this Lcom/hqa/design/test/Test; 76 4 2 e Ljava/lang/InterruptedException; StackMapTable: number_of_entries = 4 frame_type = 255 /* full_frame */ offset_delta = 75 locals = [ class com/hqa/design/test/Test, class java/lang/Object ] stack = [ class java/lang/InterruptedException ] frame_type = 4 /* same */ frame_type = 68 /* same_locals_1_stack_item */ stack = [ class java/lang/Throwable ] frame_type = 250 /* chop */ offset_delta = 2 } SourceFile: "Test.java"
Referring to the above documents, we can see the key parts:
Modification method:
public static synchronized void classLock(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC, `ACC_SYNCHRONIZED`
Decorated object code block:
public void objLock(); descriptor: ()V flags: ACC_PUBLIC Code: stack=4, locals=3, args_size=1 0: getstatic #13 // Field lock:Ljava/lang/Object; 3: dup 4: astore_1 5: `monitorenter` 6: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 9: new #26 // class java/lang/StringBuilder ....... 80: aload_1 81: `monitorexit` 82: goto 88 85: aload_1 86: `monitorexit`
It can be seen from the bytecode that the implementation of the synchronization statement block uses the monitorenter and monitorexit instructions, in which the monitorenter instruction points to the start position of the synchronization code block, and the monitorexit instruction indicates the end position of the synchronization code block. When the monitorenter instruction is executed, the current thread will try to obtain the holding right of the corresponding monitor of the objectref (i.e. the object lock). When the objectr If the entry counter of EF's monitor is 0, the thread can successfully obtain the monitor, and set the counter value to 1, and the lock is successful. If the current thread already has the ownership of the monitor of objectref, it can re-enter the monitor (about re-entry will be analyzed later), and the value of the counter will be increased by 1. If other threads already have the ownership of the monitor of objectref, the current thread will be blocked until the executing thread finishes executing, that is, the monitorexit instruction is executed, the executing thread will release the monitor (lock) and set the counter value to 0, and other threads will have the chance to hold the monitor. It is worth noting that the compiler will ensure that every monitorenter instruction that is invoked in the method will execute its corresponding monitorexit instructions regardless of the way the method is completed, regardless of whether the method ends normally or ends. In order to ensure that the monitorcenter and monitorexit instructions can still be executed correctly when the method exception is completed, the compiler will automatically generate an exception handler, which declares that all exceptions can be handled, and its purpose is to execute the monitorexit instructions. It can also be seen from the bytecode that there is an additional monitorexit instruction, which is the instruction to release the monitor executed at the end of the exception.
6.3 summary
There is no monitorenter instruction and monitorexit instruction for the synchronized decorated method. Instead of the monitorenter instruction and monitorexit instruction, it is the ACC synchronized identity, which indicates that the method is a synchronous method. The JVM uses the ACC synchronized access flag to identify whether a method is declared as a synchronous method, so as to execute the corresponding synchronous call. Monitorexit occurs twice because even the party If the lock is abnormal, the lock should be released normally.
7. About lock elimination
Lock elimination refers to the removal of locks that cannot compete for shared resources by running fewer scans even when the JVM is compiled.
Through lock elimination, meaningless lock requests can be saved
For example, when using StringBuffer in a single thread, synchronization is unnecessary. At this time, the JVM can eliminate unnecessary locks based on escape analysis count at runtime.
8. Reentry of synchronized
In terms of the design of mutex locks, when a thread tries to operate the critical resources of an object lock held by other threads, it will be blocked. However, when a thread requests again to hold the critical resources of an object lock, this situation belongs to reentry lock, and the request will succeed. Synchronized in java is based on the atomic internal locking mechanism, which is reentrant, Therefore, when a thread calls the synchronized method, it is allowed to call another synchronized method of the object in the body of the method, that is to say, a thread gets an object lock and requests the object lock again. This is the reentrant nature of synchronized.
9. About wait and notify
Look at a piece of code:
package com.hqa.design.test; public class Test { /** * An instance lock */ private final Object lock = new Object(); public Object getLock(){ return lock; } /** * Lock object is lock */ public void lock1() { synchronized (lock) { try { System.out.println(Thread.currentThread().getName() + "Get into lock1 Method,Dormancy 3 s"); Thread.sleep(3000); System.out.println(Thread.currentThread().getName() + ",3s Then release the lock"); lock.wait(); lock.notifyAll(); Thread.sleep(7000); System.out.println(Thread.currentThread().getName() + "Sign out lock Method"); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * Lock object is lock */ public void lock2() { synchronized (lock) { try { System.out.println(Thread.currentThread().getName() + "Get into lock2 Method,Dormancy 10 s"); Thread.sleep(10000); lock.notifyAll(); System.out.println(Thread.currentThread().getName() + ",Release lock,Sign out lock2 Method"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { Test test = new Test(); Thread t1 = new Thread(() -> { test.lock1(); }); t1.start(); Thread t2 = new Thread(() -> { test.lock2(); }); t2.start(); } }
Output:
Thread-0 enters lock1 method and sleeps for 3s Release lock after Thread-0,3s Thread-1 enters lock2 method, sleep for 10s Thread-1, release lock, exit lock2 method Thread-0 exits lock method
The lock object is contested by t1 and t2. In this case, t1 thread await during execution. This operation will release the lock lock, and then inform all other threads to seize the lock again. At this time, t2 will seize the lock, continue execution, and then inform other threads to execute again. We will talk about thread communication in a separate module with limited space, so that's the end of this chapter.
Reference link: https://blog.csdn.net/javazejian/article/details/72828483