Synchronized
Synchronized is a code synchronizer that modifies common methods, static methods, and blocks of code, but with different granularity.
-
Decorates a normal method: the locked object is the object on which the method is currently called. There is no competition between different objects.
-
Modifies static methods: the lock object is the class, and there are competitions between different objects.
-
Modifiers: locks this object in synchronized (object).
In a multi-threaded environment, when multiple threads concurrently operate on the same shared resource, thread safety issues may occur.
public class MyTest {
private static int a = 0;
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i=0; i<100; i++){new Thread(){
@Override
public void run() {
try {
countDownLatch.await();
for(int m=0; m<10000;m++){
a++;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
countDownLatch.countDown();
try {
Thread.sleep(10000);
} catch(InterruptedException e) { e.printStackTrace(); } System.err.println(a); }}Copy the code
After many attempts:
983386
Copy the code
In this case, we have 100 threads open (actually we can’t open that many, so I’ll leave that one out), and each thread adds a 10,000 times, so it should be 1000000. Why the undercounting? Why is thread-safety a problem?
1. What is thread safety?
In the previous article, we discussed the JMM memory model. Each thread has its own working memory. To operate on a shared variable, it must read and load from main memory to the working memory. If all the threads read the same value from the working memory, they will then store and write back to the main memory. This is often referred to as a thread safety problem.
Imagine if the core code at work had a thread-safety problem, that would be the end. As it happens, I have recently been doing an order related demand (assuming it is a single point). If the inventory is less deducted, it will cause a wave of customer complaints
2. How can I avoid thread-safety issues?
- The article said
Synchronized
, which can lock methods, lock blocks of code. ReentrantLock
, it isAQSA pessimistic lock with exclusive, reentrant, fair and unfair properties.CAS
The operation, which stands for Compred and Swap, is a lockless technique that will be covered in more detail in the next article on AQS.
As a curious kid, you certainly want to know how Synchronized’s low-level implementation works
So, sweetie!! Read on with your curiosity.
The underlying principle of Synchronized
Since Synchronized is a modifier, we must look at the compiled bytecode
Take a simple code example:
1. The lock method
public class MyTest {
private static int a = 0;
public synchronized static void main(String[] args){ a++; }}Copy the code
Javap -v mytest. class = ‘ACC_SYNCHRONIZED’; MyTest = ‘ACC_SYNCHRONIZED’;
public class com.example.spring.jvmTest.MyTest
minor version: 0
major version: 52
flags: ACC_PUBLIC.ACC_SUPER
Constant pool# 1:= Methodref #4.#21 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#22 // com/example/spring/jvmTest/MyTest.a:I
#3 = Class #23 // com/example/spring/jvmTest/MyTest
#4 = Class #24 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/example/spring/jvmTest/MyTest;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;) V #16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 <clinit>
#19 = Utf8 SourceFile
#20 = Utf8 MyTest.java
#21 = NameAndType #7: #8 // "<init>":()V
#22 = NameAndType #5: #6 // a:I
#23 = Utf8 com/example/spring/jvmTest/MyTest
#24 = Utf8 java/lang/Object
{
public com.example.spring.jvmTest.MyTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/spring/jvmTest/MyTest;
public static synchronized void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;) Vflags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field a:I
3: iconst_1
4: iadd
5: putstatic #2 // Field a:I
8: return
LineNumberTable:
line 8: 0
line 9: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_0
1: putstatic #2 // Field a:I
4: return
LineNumberTable:
line 5: 0
}
Copy the code
2. Lock the code block
public class MyTest {
private static int a = 0;
private static Object object = new Object(a); publicstatic void main(String[] args){ synchronized (object){ System.err.println(++a); }}}Copy the code
Javap -v mytest. class specifies that the lock block has a new Monitorenter at the entry and a new Monitorexit at the exit, as follows:
public class com.example.spring.jvmTest.MyTest
minor version: 0
major version: 52
flags: ACC_PUBLIC.ACC_SUPER
Constant pool# 1:= Methodref #6.#30 // java/lang/Object."<init>":()V
#2 = Fieldref #7.#31 // com/example/spring/jvmTest/MyTest.object:Ljava/lang/Object;
#3 = Fieldref #32.#33 // java/lang/System.err:Ljava/io/PrintStream;
#4 = Fieldref #7.#34 // com/example/spring/jvmTest/MyTest.a:I
#5 = Methodref #35.#36 // java/io/PrintStream.println:(I)V
#6 = Class #37 // java/lang/Object
#7 = Class #38 // com/example/spring/jvmTest/MyTest
#8 = Utf8 a
#9 = Utf8 I
#10 = Utf8 object
#11 = Utf8 Ljava/lang/Object;
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 Lcom/example/spring/jvmTest/MyTest;
#19 = Utf8 main
#20 = Utf8 ([Ljava/lang/String;) V #21 = Utf8 args
#22 = Utf8 [Ljava/lang/String;
#23 = Utf8 StackMapTable
#24 = Class #22 // "[Ljava/lang/String;"
#25 = Class #37 // java/lang/Object
#26 = Class #39 // java/lang/Throwable
#27 = Utf8 <clinit>
#28 = Utf8 SourceFile
#29 = Utf8 MyTest.java
#30 = NameAndType #12: #13 // "<init>":()V
#31 = NameAndType #10: #11 // object:Ljava/lang/Object;
#32 = Class #40 // java/lang/System
#33 = NameAndType #41: #42 // err:Ljava/io/PrintStream;
#34 = NameAndType #8: #9 // a:I
#35 = Class #43 // java/io/PrintStream
#36 = NameAndType #44: #45 // println:(I)V
#37 = Utf8 java/lang/Object
#38 = Utf8 com/example/spring/jvmTest/MyTest
#39 = Utf8 java/lang/Throwable
#40 = Utf8 java/lang/System
#41 = Utf8 err
#42 = Utf8 Ljava/io/PrintStream;
#43 = Utf8 java/io/PrintStream
#44 = Utf8 println
#45 = Utf8 (I)V
{
public com.example.spring.jvmTest.MyTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/spring/jvmTest/MyTest;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;) Vflags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: getstatic #2 // Field object:Ljava/lang/Object;
3: dup
4: astore_1
5: monitorenter
6: getstatic #3 // Field java/lang/System.err:Ljava/io/PrintStream;
9: getstatic #4 // Field a:I
12: iconst_1
13: iadd
14: dup
15: putstatic #4 // Field a:I
18: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
21: aload_1
22: monitorexit
23: goto 31
26: astore_2
27: aload_1
28: monitorexit
29: aload_2
30: athrow
31: return
Exception table:
from to target type
6 23 26 any
26 29 26 any
LineNumberTable:
line 10: 0
line 11: 6
line 12: 21
line 13: 31
LocalVariableTable:
Start Length Slot Name Signature
0 32 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 26
locals = [ class "[Ljava/lang/String; .class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: iconst_0
1: putstatic #4 // Field a:I
4: new #6 // class java/lang/Object
7: dup
8: invokespecial #1 // Method java/lang/Object."<init>":()V
11: putstatic #2 // Field object:Ljava/lang/Object;
14: return
LineNumberTable:
line 5: 0
line 7: 4
}
SourceFile: "MyTest.java"
Copy the code
3. Summary
From the results of the compilation
- Synchronized code blocks are passed
monitorenter
andmonitorexit
Each synchronization object has its own Monitor. The locking process is as follows:
- The synchronization method is done by adding
ACC_SYNCHRONIZED
Before calling a method on this class, the JVM checks to see if the class has beenACC_SYNCHRONIZED
If yes, the executing thread gets it firstMonitor, the corresponding method can be executed only when the method is successfully obtained. After the execution, the Monitor can be released. During the execution of the method, no other thread can obtain the same methodMonitorObject, the blocked thread is suspended until the CPU reschedule, which causes other threads to switch back and forth between user mode and kernel mode, which has a significant impact on performance.
Synchronized is an implementation based on the JVM’s built-in lock Monitor, which synchronizes methods with code blocks by entering and exiting the Monitor. Monitor, in turn, relies on the operating system’s Mutex locks, which are heavyweight locks with low performance. So the JVM has made significant improvements since version 1.5, such as lock coarsening, lock elimination, and lock bloating to reduce the cost of locking.
4. What is Monitor?
Java is an object-oriented language, so Monitor is no exception. In the HotSpot virtual machine, Monitor is defined as ObjectMonitor, which has the following source code (implemented by C++):
Two collections are maintained in ObjectMonitor, WaitSet(to which threads in a wait state are added) and EntryList(to which blocked threads waiting for a lock are added) are used to hold a list of ObjectWaiter objects (each thread waiting for a lock is wrapped as ObjectWaiter), The owner pointer points to the thread that currently holds the ObjectMonitor object. When multiple threads access the synchronized code, the steps are as follows:
- First, enter the EntryList collection. When the thread gets the object’s monitor, the owner pointer points to the current thread that got the monitor, and incrementing the count in the monitor by 1.
- If a thread calls the wait () method, it releases the Monitor it currently holds, the owner pointer points to null, the count is reduced by 1, and the thread enters the WaitSet waiting to be awakened.
- If the current thread finishes executing, the current Monitor is also released. The owner pointer points to NULL, and the count is reduced by 1, so that other threads can obtain the Monitor.
Note: Methods such as notify/notifyAll/wait are also used with Monitor and must be used in synchronization blocks.
JVM optimization of Synchronized
1.Synchronized and AQS
Have you ever wondered why there are two synchronization locks built into the JDK? Why AQS when you have Synchronized?
After consulting materials, it was found that Synchronized was a heavyweight lock with low performance before JDK5. To solve this problem, Dog Li (Doug, Li) hand-wrote a set of GUI and sent out a package, in which AQS performance was far superior to Synchronized. Later, after JDK was acquired by Oracle, Oracle is a powerful company, and has made significant improvements to Synchronized (which we’ll talk about later), so that its performance is comparable to AQS. The GUI package was later acquired by Oracle, which resulted in the JDK’s two built-in concurrency tools today
2. Spin locks and adaptive spin locks
Spin: When thread A requests A lock, the lock is being held by another thread, but instead of immediately blocking, thread A requests the lock (spin). The aim is because most of the time the thread holding the lock will soon release the lock, thread A can try always request A lock, there is no need to hang up CPU time slice, because the thread is suspended and then to awaken the process is costly (need to experience many switching between user mode and kernel mode), if the thread A spin specified time haven’t get the lock, It still hangs.
Adaptive spin: Adaptive spin is an upgrade or optimization of spin. The spin time is no longer fixed, but is determined by the last spin time on the same lock and the state of the lock owner. For example, if a thread spins successfully, it will spin more next time, because the JVM will allow more spins because it thinks that since it succeeded last time, it will likely succeed this time. On the other hand, if the spin is rarely successful for a lock, the number of spins will be reduced or even ignored when the lock is acquired in the future to avoid wasting CPU resources. With adaptive spin, the JVM’s prediction of the state of a program lock will become more accurate as information about program execution and performance monitoring improves.
3. Coarsening and elimination of locks
Lock elimination: Lock elimination is when the VIRTUAL machine, through escape analysis during JIT just-in-time compilation, removes locks that have not been read or written to shared variables in synchronized code blocks.
Look at the following code: Although the append method of StringBuffer is Synchronized, the escape analysis shows that StringBuffer is a local variable and will not escape the method, that is, it will not be referenced externally, so the test method must be thread-safe and lock elimination occurs.
public class MyTest {
public static void main(String[] args) {
new MyTest().test();
}
public void test(){
StringBuffer sb = new StringBuffer();
sb.append("hello boom"); }}Copy the code
Lock coarsening: When using locks, you need to keep the synchronization block’s lock range as small as possible and the number of locks as small as possible. If the JVM detects a string of fragmented operations that lock the same object, it will extend (coarsening) the scope of lock synchronization to the outside of the entire operation.
Look at the code below: The append method of StringBuffer is modified by Synchronized. If the append method is called twice, the JVM will acquire two locks, and performance will be significantly degraded, so the JVM coarsened the code so that it only needs to acquire the lock once to lock both append methods.
public class MyTest {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer();
sb.append("hello");
sb.append("boom"); }}Copy the code
4. The lock expansion escalates
There are four types of Synchronized status (unidirectional, sequential) : no-lock, biased, lightweight, and heavyweight.
In the composition of the object, Mark Word will record the state of the lock. Through these states, the lock is recorded and the lock is upgraded. This article has detailed introduction of the composition of the object: juejin.cn/post/694723…
Biased locking:
In order to reduce the cost of lock acquisition by the same thread (which involves CAS operation and is relatively time-consuming), bias lock is introduced if there is no multi-thread contention lock and the lock is always acquired by the same thread.
Biased locking thought is, if a thread to get the lock, then lock into biased locking mode, Mark at this time the structure of the Word will become biased locking state, when the thread again to get the same lock, without any experience the process of acquiring a lock, directly to get the lock, so it saves a lot of acquiring a lock operation, thus improve the performance of the program.
However, in the case of fierce lock competition, the thread that obtains the lock may be different each time, which leads to biased lock failure and upgrade to lightweight lock.
Lightweight locks: Biased locks fail and will be upgraded to lightweight locks. Mark Word’s structure will also change to a lightweight lock state. A lightweight lock is used when multiple threads execute synchronized blocks of code alternately. If multiple threads are competing for the same lock at the same time, a lightweight lock will be upgraded to a heavyweight lock.
Heavyweight Lock: When we talk about a heavyweight Lock, we call the underlying Mutex Lock of the operating system
Below is the process that the lock that finds on the net upgrades, I feel quite detailed, draw lessons from.