

从字节码看 synchronized 关键字是怎么工作的
source link: https://www.boris1993.com/how-synchronized-works-in-java.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

从字节码看 synchronized 关键字是怎么工作的
昨天面试的时候被问到 Java 中的 synchronized
关键字是什么原理,虽然凭着记忆打出来是通过控制对象头的 Monitor 来实现,但是毕竟没吃透这个知识点,还是没啥底气。干脆,这次就从字节码上看看,用了 synchronized
关键字的方法,到底是怎么执行的。
说起 synchronized
的最简单的使用场景,我马上就想起双检单例模式。
public class Test {
private static volatile Test INSTANCE;
private Test() {
}
public static Test getInstance() {
if (INSTANCE == null) {
synchronized (Test.class) {
if (INSTANCE == null) {
INSTANCE = new Test();
}
}
}
return INSTANCE;
}
public void print() {
System.out.println("test");
}
}
反编译成字节码
把 Test
类先编译了,然后用 javap -c Test.class
反编译,就能看到这个类的字节码了。
Compiled from "Test.java"
public class Test {
public static Test getInstance();
Code:
0: getstatic #7 // 把静态变量INSTANCE加载到栈
3: ifnonnull 37 // 如果值不是null,那么跳转到标签37
6: ldc #8
8: dup
9: astore_0
10: monitorenter // 进入synchronized块
11: getstatic #7 // 把静态变量INSTANCE加载到栈
14: ifnonnull 27 // 如果值不是null,那么跳转到标签27
17: new #8 // new一个Test对象
20: dup
21: invokespecial #13 // 执行构造函数
24: putstatic #7
27: aload_0
28: monitorexit // 退出synchronized块
29: goto 37
32: astore_1
33: aload_0
34: monitorexit
35: aload_1
36: athrow
37: getstatic #7 // Field INSTANCE:LTest;
40: areturn
Exception table:
from to target type
11 29 32 any
32 35 32 any
public void print();
Code:
0: getstatic #14 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #20 // String test
5: invokevirtual #22 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
注意看 10: monitorenter
和 28: monitorexit
这两条字节码,这就是 synchronized
关键字实际做了的事。
Java 对象头和 Monitor
要说明白 monitorenter
和 monitorexit
实际干了点啥,那就得先整明白 Java 对象的对象头。
一个 Java 对象,在内存中的布局包括三块区域:对象头、实例数据、和对齐填充。
别的东西咱们先不看,只看对象头这部分。对象头的最后 2bit 就存储了锁的标志位。
至于 Monitor,Java 官方文档是这么描述的:
Synchronization is built around an internal entity known as the intrinsic lock or monitor lock. (The API specification often refers to this entity simply as a “monitor.”) Intrinsic locks play a role in both aspects of synchronization: enforcing exclusive access to an object’s state and establishing happens-before relationships that are essential to visibility.
Every object has an intrinsic lock associated with it. By convention, a thread that needs exclusive and consistent access to an object’s fields has to acquire the object’s intrinsic lock before accessing them, and then release the intrinsic lock when it’s done with them.
同步是围绕着一个名为 “内在锁” 或 “monitor 锁” 的机制构建的。(API 规范文档中,通常会称其为 “monitor”)
内在锁一方面保证了针对一个对象的专属访问权限,另一方面保证了对可见性很重要的 happens-before 原则。
每个对象都会有一个与其相关联的内在锁。按照约定,如果一个线程需要持续持有对一个对象的独家访问权限,那么这个线程必须先获得到这个对象的内在锁,然后在执行完毕后释放掉这个内在锁。
代码执行到 monitorenter
指令,说明开始进入 synchronized
代码块,这时候 JVM 会尝试获取这个对象的 monitor
所有权,即尝试加锁;而执行到 monitorexit
指令,就说明要么 synchronized
代码块执行完毕,要么代码执行的时候抛出了异常,这时候 JVM 就会释放这个对象的 monitor
所有权,即释放锁。
继续深入细节
上面说的也是云里雾里的,咱继续往深处挖,看看具体的实现。
Monitor
这个东西,看 Java 源码找不到,得找虚拟机的 C++ 源码。比如我们常用的 HotSpot 虚拟机中,Monitor
是由 ObjectMonitor
类实现的:
// 为了解释方便,仅抄录了相关的代码,并重排了位置
class ObjectMonitor {
public:
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
private:
volatile intptr_t _count; // reference count to prevent reclaimation/deflation
// at stop-the-world time. See deflate_idle_monitors().
// _count is approximately |_WaitSet| + |_EntryList|
// 等待锁的线程会被封装成ObjectWaiter对象
protected:
void * volatile _owner; // 一个指针,指向当前拥有锁的线程
ObjectWaiter * volatile _WaitSet; // 一个队列,保存着waiting状态的线程
ObjectWaiter * volatile _EntryList ; // 一个队列,保存着因等待锁而被阻塞的线程
}
当多个线程同时访问一段 synchronized
代码时,会发生这些操作:
- 线程首先会进入
_EntryList
,在该线程获取到对象的monitor
之后,_owner
会指向这个线程,然后_count
计数器加一。- 如果得到
monitor
的这个线程调用了wait()
方法,那么这个线程将会释放掉 monitor 的所有权,_owner
变量变回 NULL,_count
计数器也会减一,同时这个线程会进入_WaitSet
等待被唤醒。 - 如果这个线程执行完毕,那么它也将释放
monitor
,并复位_count
的值,这样其他的线程也就可以获得monitor
来加锁了。
- 如果得到
- 上一个线程释放掉
monitor
后,_EntryList
中的线程就会开始争抢monitor
,具体哪个线程能成功得到monitor
是不确定的。
而正因为 Monitor 对象存在于每个 Java 对象头的 mark word
中,所以每个 Java 对象都可以用作锁。
Recommend
-
33
从字节码看java中 this 隐式传参具体体现,也发现了 static 与 非 static 方法的区别所在! static与非static方法都是存储java的方法区。在static 方法中,没有this引用,因此无法使用当前类中所定义的变量,而非static方法则会...
-
10
4. synchronized工作原理 发表于...
-
7
聊聊 Java 中绕不开的 Synchronized 关键字 Posted on 2021-06-20...
-
5
synchronized关键字 什么是synchronized JDK官网对synchronized关键字有个比较权威的解释。 Synchronized keyword enable a simple strategy for preventing thread interference and memory consistency errors: if an object...
-
4
记一次Synchronized关键字使用不合理,导致的多线程下线程阻塞问题排查 | HeapDump性能社区记一次Synchronized关键字使用不合理,导致的多线程下线程阻塞问题排查杭盖S...
-
7
面试题:用过final关键字吗?它有什么作用 面试考察点
-
5
多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问时可能出现的问题。 同步机制可以使用synchronized关键字实现。 当synchronized关键字修饰一个方法的时候...
-
7
想必在面试中经常会被问到Synchronized关键字,它有什么特性,原理什么 它的主要特性是同步锁、非公平锁、阻塞锁、可以保证线程安全(可见性、原子性、有序性) JDK1.6之后对Synchronized有优化,有个锁升级过程
-
7
作者:小牛呼噜噜 | https://xiaoniuhululu.com 计算机内功、JAVA底层、面试、职业成长相关资料等更多精彩文章在公众号「
-
25
🔍🌐 您是否常常遇到 #YouTube 输入验证码看不懂的问题? https://www.chenweiliang.com/cwl-30966.html 对于 #IPv6 用户,这是一大挑战。别担心,我们为您提供了解决方案!🚀 点击我们的链接,获取详细信息。一起来解决这个问题吧!💡
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK