Simple understanding and use of Java Synchronized
Synchronized
0. Background
As shown in the figure above, for example, in the King of Glory program, our team has two threads to count the economy of descendants and Angela respectively.
- A thread reads the total economy of the current team from the memory and loads it into the local stack of the thread, after performing the +100 operation
- At this time, the B thread also takes out the economic value + 200 from the memory, and writes 200 back to the memory
- Just after the execution of thread B, thread A writes 100 back into the memory
There is a problem, our team's economy should be 300, but the memory is 100.
How does synchronized solve this problem?
Two threads, A thread makes the team economy +1, B thread makes the team economy +2, execute a thousand times respectively, the correct result should be 3000, but the result is 2845.
This is the code and console output after locking.
1. What is a synchronized lock?
The synchronized keyword solves the synchronization of resource access between multiple threads. The synchronized keyword can ensure that only one thread can execute the method or code block modified by it at any time.
- Synchronized is a keyword in Java that can lock code blocks (methods)
public synchronized void test() {
// 关注公众号Java3y
// doSomething
}
- synchronized is a mutex
- Only one thread is allowed to enter the locked code block at a time
- synchronized is a built-in lock/monitor lock
- Every object in Java has a built-in lock (monitor, which can also be understood as a lock mark), and synchronized is used
对象的内置锁(监视器)
to lock code blocks (methods)!
- Every object in Java has a built-in lock (monitor, which can also be understood as a lock mark), and synchronized is used
In addition, in earlier versions of Java, synchronized was a heavyweight lock, which was inefficient. why?
Because the monitor lock (monitor) is implemented
Mutex Lock
depending , Java threads are mapped to the native threads of the operating system. If you want to suspend or wake up a thread, you need the help of the operating system to complete it, and the operating system needs to switch from user mode to kernel mode when switching between threads. The transition between these states takes a relatively long time, and the time cost Relatively high.
Fortunately, after Java 6, Java officials have greatly optimized synchronized from the JVM level, so the current synchronized lock efficiency is also optimized very well. JDK1.6 introduces a lot of optimizations to the implementation of locks, such as spin locks, adaptive spin locks, lock elimination, lock coarsening, biased locks, lightweight locks and other technologies to reduce the overhead of lock operations.
Therefore, you will find that at present, whether it is various open source frameworks or JDK source codes, the synchronized keyword is widely used.
2. Features
- The first thing to be clear is that Java multithreaded locks are all object-based, and every object in Java can be used as a lock. The class locks we often hear are actually object locks.
- A Java class has only one Class object (there can be multiple instance objects, and multiple instances share this Class object), and the Class object is also a special Java object. So the class lock we often say is actually the lock of the Class object.
- synchronized is a mutex that only allows one thread to enter the locked code block at a time
- Synchronized is a built-in lock monitor lock. Every object in Java has a built-in lock (monitor, which can also be understood as a lock mark), and synchronized uses the built-in lock (monitor) of the object to block code blocks (method) locked! (What is locked is the object, but what we synchronize is the method/code block)
3. Advantages
4. Usage scenarios
The use of Synchronized in Java
Synchronized is generally used to modify three things:
- 1. Synchronized modification
普通同步方法
: the current instance object of the lock object; - 2. Synchronized modification
静态同步方法
: the lock object is the current Class object; - 3. Synchronized modification
同步代码块
: the lock object is the object configured in the brackets after Synchronized. This object can be an object (xlock) or a class (Xlock.class);
public class SynchronizedSample {
private final Object lock = new Object();
private static int money = 0;
//非静态方法
public synchronized void noStaticMethod(){
money++;
}
//静态方法
public static synchronized void staticMethod(){
money++;
}
public void codeBlock(){
//代码块
synchronized (lock){
money++;
}
}
}
4.1. Modified instance method:
It acts on 当前对象
instance locking, and obtains the lock of the current object instance before entering the synchronization code
@Override
public synchronized void run() {
Date startDate = DateUtil.date();
for (int i = 0; i < 5; i++) {
try {
System.out.println("线程 :" + Thread.currentThread().getName() + " 当前计数器 :" + (counter++));
System.out.println("开始时间 :" + startDate + " 当前时间 :" + DateUtil.date());
System.out.println();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.2. Modified static method:
That is, to 当前类
add a lock, it will act on all object instances of the class, and the lock of the current class must be obtained before entering the synchronization code.
- Because a static member does not belong to any instance object, it is a class member (static indicates that this is a static resource of the class, no matter how many objects are new, there is only one copy).
Therefore, if a thread A calls a non-static synchronized method of an instance object, and thread B needs to call a static synchronized method of the class to which the instance object belongs, it is allowed and mutual exclusion will not occur, because accessing the lock occupied by the static synchronized method It is the lock of the current class, and the lock occupied by accessing the non-static synchronized method is the current instance object lock.
package com.example.lock.syn.demo02;
import cn.hutool.core.date.DateUtil;
import java.util.Date;
/**
* synchrosnized 关键字测试
* 同步-静态方法
*/
public class SynchronizedDemo3 implements Runnable {
private static int counter = 1;
/**
* 静态的同步方法
*/
public synchronized static void method() {
Date startDate = DateUtil.date();
for (int i = 0; i < 5; i++) {
try {
System.out.println("线程 :" + Thread.currentThread().getName() + " 当前计数器 :" + (counter++));
System.out.println("开始时间 :" + startDate + " 当前时间 :" + DateUtil.date());
System.out.println();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
method();
}
}
public static void main(String[] args) {
SynchronizedDemo3 syncThread1 = new SynchronizedDemo3();
SynchronizedDemo3 syncThread2 = new SynchronizedDemo3();
Thread thread1 = new Thread(syncThread1, "sync-thread-1");
Thread thread2 = new Thread(syncThread1, "sync-thread-2");
thread1.start();
thread2.start();
}
Result description
- syncThread1 and syncThread2 are two objects of SyncThread, but thread synchronization is maintained when thread1 and thread2 are executed concurrently.
- This is because the static method method is called in run, and the static method belongs to the same class, so syncThread1 and syncThread2 are equivalent to using the same lock.
4.3. Modified code block:
Specify the lock object to lock the given object/class. synchronized(this | object)
Indicates that a lock on the given object is to be acquired before entering the synchronized code base.
synchronized(类.class)
Indicates that the lock of the current class must be obtained before entering the synchronization code
/**
* synchrosnized 关键字测试
* 同步代码块
*/
public class SynchronizedDemo1 implements Runnable {
/**
* 全局变量
* 创建一个计数器
*/
private static int counter = 1;
@Override
public void run() {
Date startDate = DateUtil.date();
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println("线程 :" + Thread.currentThread().getName() + " 当前计数器 :" + (counter++));
System.out.println("开始时间 :" + startDate + " 当前时间 :" + DateUtil.date());
System.out.println();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
SynchronizedDemo1 syncThread = new SynchronizedDemo1();
Thread thread1 = new Thread(syncThread, "sync-thread-1");
Thread thread2 = new Thread(syncThread, "sync-thread-2");
thread1.start();
thread2.start();
}
}
The results show that when two concurrent threads (thread1 and thread2) access the synchronized code in the same object (syncThread):
- Only one thread can be executed at the same time, and the other thread is blocked, and the code block must be executed after the current thread finishes executing the code block.
- Thread1 and thread2 are mutually exclusive, because the current object will be locked when the synchronized code block is executed, the object lock can only be released after the code block is executed, and the next thread can execute and lock the object.
slightly modified
public static void main(String[] args) {
SynchronizedDemo1 syncThread1 = new SynchronizedDemo1();
SynchronizedDemo1 syncThread2 = new SynchronizedDemo1();
Thread thread1 = new Thread(syncThread1, "sync-thread-1");
Thread thread2 = new Thread(syncThread2, "sync-thread-2");
thread1.start();
thread2.start();
}
It can be seen from the figure that both threads create a new object to execute, so there are also two locks, so the execution method is executed at the same time.
4.4. Note
- 1. When using
synchronized
the modified non-static method or usingsynchronized
the modified code block as an instance object, different objects of the same class have their own locks, so they will not block each other. - 2.
synchronized
When using modified classes and objects, since class objects and instance objects have their own monitor locks, they will not block each other. - 3. When using synchronized to modify an instance object, if a thread is accessing a synchronized method of the instance object, other threads not only cannot access the synchronized method, but also other synchronized methods of the object, because an object has only one monitor lock object, but other threads can access the object's non-synchronized methods.
- 4. When thread A accesses the non-static synchronized method of the instance object, thread B can also access the static synchronized method of the instance object at the same time, because the former acquires the monitor lock of the instance object, while the latter acquires the monitor lock of the class object , there is no mutually exclusive relationship between the two.
4.5, double check lock implementation object singleton (thread safety)
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
4.6. Summary
- The synchronized keyword is added to the
static 静态方法
andsynchronized(class)
code blocks to lock the Class class. - The synchronized keyword is added to the instance method to lock the object instance.
- Try not to use synchronized(String a) because in the JVM, the string constant pool has a caching function!
5. The underlying principle
Synchronized analysis - if you are willing to peel off my heart layer by layer
5.1, object header
When a Java object is stored in memory, it consists of the following three parts:
1. Object header
2. Instance data: The instance data of the object is the attributes and their values that can be seen in the java code
3. Align padding bytes: Because the JVM requires that the memory size occupied by java objects should be a multiple of 8bit, so there are a few bytes in the back to pad the size of the object to a multiple of 8bit, and there is no special function.
Java's object header consists of the following three parts:
1,Mark Word
2. Pointer to the class: the class data of the Java object is stored in the method area
3. Array length (only for array objects): the length of the data is 32bit in both 32-bit and 64-bit JVM
5.1.1、Mark Word
Detailed explanation of Java's object header and object composition
Mark Word records the information related to the object and the lock. When the object is synchronized
regarded as a synchronization lock by the keyword, a series of operations around the lock are related to the Mark Word.
- The length of Mark Word in 32-bit JVM is 32bit, and the length in 64-bit JVM is 64bit.
- Mark Word stores different content in different lock states. It is stored in the 32-bit JVM like this: the lock flag bits are all , but
the first 1 bit distinguishes whether it is a lock-free state or a lock-biased state.无锁和偏向锁
01
Versions after JDK1.6 have the concept of lock upgrade when dealing with synchronization locks. The JVM starts with biased locks for synchronization locks. As the competition becomes more and more intense, the processing method is upgraded from biased locks to lightweight locks. Eventually upgrade to a heavyweight lock.
JVM generally uses locks and Mark Word like this:
- 1.
没有锁
When, this is an ordinary object, Mark Word records the HashCode of the object, the lock flag bit is01
, and the bit that is biased towards the lock is0
. - 2. When the object is regarded as a synchronous lock and
有一个线程
A grabs the lock, the lock flag is still 01, but the bit of whether the lock is biased is changed to 1, and the first 23 bits record the thread id that grabbed the lock, indicating that it has entered the biased lock state.- When
线程A再次
trying to acquire the lock, the JVM finds that the flag of the synchronization lock object is01
whether the biased lock is 1, that is, the biased state. The thread id recorded in MarkWord is the thread A's own id, indicating that thread A has obtained the biased lock. , you can execute code that is synchronously locked.
- When
- 3. When thread B tries to acquire the lock, the JVM finds that the synchronization lock is in a biased state, but the thread id in the Mark Word record is not B, then thread B will try to acquire the lock first, and the lock acquisition operation here may
CAS操作
succeed Yes, because thread A generally does not automatically release the biased lock. If the lock is successfully grabbed, change the thread id in the Mark Word to the id of thread B, which means that thread B has obtained the biased lock and can execute the synchronization lock code. If the lock grab fails, go to step 4. - 4. If the lock grab fails in the biased lock state, it means that the current lock has certain competition, and the biased lock will be upgraded to a lightweight lock.
开辟一块单独的空间
The JVM will save it in the thread stack of the current thread指向对象锁Mark Word的指针
, and at the same time save a pointer to this space in the object lock Mark Word. The above two save operations are bothCAS操作
. If the save is successful, it means that the thread has grabbed the synchronization lock, and the lock flag in the Mark Word is changed to00
, and the synchronization lock code can be executed. If the save fails, it means that the lock grab failed and the competition is too fierce, go to step 5. - 5. 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 just represents continuous retrying and trying to grab the lock. Starting from JDK1.7, the spin lock is enabled by default, and the number of spins is determined by the JVM. If the lock grabbing succeeds, execute the synchronization lock code, and if it fails, proceed to step 6.
- 6. After retrying the spin lock, if the lock grab still fails, the synchronization lock will be upgraded to a heavyweight lock, and the lock flag will be changed to 10. In this state, threads that have not grabbed the lock will be blocked.
5.2、Monitor
What does java monitor mean, Java interview FAQ: What is the Monitor object?
Monitor can be understood as a synchronization tool or a synchronization mechanism, and is usually described as an object. Every Java object has 有一把
an invisible lock, called 内部锁
or Monitor锁
.
- Monitor is
线程私有
a data structure, each thread has an availablemonitor record
list, and there is also a global available list. - Each locked object is associated with a monitor, and there is an Owner field in the monitor to store the unique identifier of the thread that owns the lock, indicating that the lock is occupied by this thread.
- If you use synchronized to the object
加锁(重量级)
,该对象的Mark Word就被设置指向了Monitor对象的指针
How is the object associated with the monitor? Look directly at the picture:
Monitor structure:
The working mechanism of Java Monitor is shown in the figure:
To make it more vivid, here is an example:
synchronized(this){
//进入_EntryList队列
doSth();
this.wait(); //进入_WaitSet队列
}
5.3, synchronized synchronization code block
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("synchronized 代码块");
}
}
}
Use javap
the command to view SynchronizedDemo
the relevant bytecode information of the class:
- First switch to the corresponding directory of the class and execute
javac SynchronizedDemo.java
the command to generate the compiled.class
file. - Then execute
javap -c -s -v -l SynchronizedDemo.class
.
From the above we can see that:
The implementation of the synchronized block uses the monitorenter
and monitorexit
bytecode instructions,
monitorenter
A directive points to a block of synchronized code开始位置
;
monitorexit
a directive specifies a block of synchronized code结束位置
.
The JVM must ensure that monitorentry and monitorexit appear in pairs
- 1. When executing
monitorenter
the instruction , the thread tries to acquire the lock, which is to acquiremonitor
the ownership of the object monitor. - 2. During execution
monitorenter
, it will try to acquire the lock of the object. If the lock counter is 0, it means that the lock can be acquired. After acquisition, set the lock counter to 1, that is, add 1. - 3. After executing
monitorexit
the instruction , set the lock counter to 0, indicating that the lock is released. If the acquisition of the object lock fails, the current thread will block and wait until the lock is released by another thread. - 4. If the acquisition of
monitor
the object fails, the thread will enter a blocked state until other threads release the lock.
5.4, synchronized modification method
public class SynchronizedDemo2 {
public synchronized void method() {
System.out.println("synchronized 方法");
}
}
The method modified by synchronized does not have monitorenter
instructions and monitorexit
instructions, but is replaced ACC_SYNCHRONIZED
by the logo , which indicates that the method is a synchronized 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.
5.5, monitorenter, monitorexit, ACC_SYNCHRONIZED instructions
Synchronized analysis - if you are willing to peel off my heart layer by layer
5.6 Summary
1. synchronized 同步语句块
The implementation uses the monitorenter
and monitorexit
directives:
monitorenter
The instruction points to the start position of the synchronized code block,monitorexit
and the instruction indicates the end position of the synchronized code block.
2. synchronized 修饰的方法
There is no monitorenter instruction and monitorexit instruction, and instead, ACC_SYNCHRONIZED
the flag , which indicates that the method is a synchronous method.
3, but both 本质都是对对象监视器 monitor 的获取
.
6. Lock optimization
Java Concurrency - Detailed Implementation Principles of Synchronized
7. The difference between synchronized and ReentrantLock
8. The difference between Synchronized and Volatile
- 1. Performance: The volatile keyword is a lightweight implementation of thread synchronization, so the volatile performance is definitely better than the synchronized keyword .
- 2. On the scope of modification: the volatile keyword can only be used for variables and the synchronized keyword can be used to modify methods and code blocks.
- 3. In terms of characteristics: the volatile keyword can guarantee the visibility of the data, but it cannot guarantee the atomicity of the data. The synchronized keyword guarantees both.
- 4. Function: The volatile keyword is mainly used to solve the visibility of variables among multiple threads, while the synchronized keyword solves the synchronization of accessing resources between multiple threads.
reference
1. Learn about the Java lock mechanism
2. Java thread safety and lock mechanism