synchronized 关键字底层原理属于 JVM 层⾯。

同步代码块

image-20210328120105440

1
2
3
4
5
6
7
public class SynchronizedDemo {
public void method(){
synchronized (this){
System.out.println("Synchronized同步代码块");
}
}
}

使用java -v -p查看字节码文件

image-20210328120229748

从上⾯我们可以看出:

synchronized 同步语句块的实现使⽤的是monitorentermonitorexit指令,其中monitorenter指令指向同步代码块的开始位置, monitorexit指令则指明同步代码块的结束位置。

当执⾏monitorenter指令时,线程试图获取锁也就是获取对象监视器 monitor的持有权。

在 Java 虚拟机(HotSpot)中,Monitor 是基于 C++实现的,由ObjectMonitor实现的。每个对 象中都内置了⼀个 ObjectMonitor 对象。

另外, wait/notify 等⽅法也依赖于 monitor 对象,这就是为什么只有在同步的块或者⽅法 中才能调⽤ wait/notify 等⽅法,否则会抛出 java.lang.IllegalMonitorStateException的异常的 原因。

在执⾏ monitorenter 时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。

在执⾏ monitorexit 指令后,将锁计数器设为 0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外⼀个线程释放为⽌。

至于为啥会有两个monitorexit指令。是因为防止同步代码块存在异常,无法正常释放资源。相当于try-catch-finally在finally中释放资源。这也就解释了为啥synchronized为啥能自动释放资源,也就是底层帮你实现了相关步骤。

同步方法

image-20210328120923832

1
2
3
4
5
6
7
8
9
10
11
public class SynchronizedDemo {
public void method(){
synchronized (this){
System.out.println("Synchronized同步代码块");
}
}

public synchronized void method2(){
System.out.println("Synchronized同步方法");
}
}

同意查看字节码命令。

image-20210328121356254

synchronized 修饰的⽅法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是ACC_SYNCHRONIZED标识,该标识指明了该⽅法是⼀个同步⽅法。JVM 通过该ACC_SYNCHRONIZED访问标志来辨别⼀个⽅法是否声明为同步⽅法,从⽽执⾏相应的同步调⽤。

总结

  1. synchronized 同步语句块的实现使⽤的是 monitorenter 和 monitorexit 指令
  2. 其中 monitorenter 指令指向同步代码块的开始位置, monitorexit 指令则指明同步代码块的结束位 置。
  3. synchronized 修饰的⽅法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是ACC_SYNCHRONIZED标识,该标识指明了该⽅法是⼀个同步⽅法。 不过两者的本质都是对对象监视器monitor的获取。

参考JavaGuide哥面试突击版。