底层实现
synchronized底层实现
详细参考:杨晓峰极客时间上的课程《Java核心技术面试精讲》:第16讲 | synchronized底层如何实现?什么是锁的升级、降级
-
synchronized 代码块是由一对儿 monitorenter/monitorexit 指令实现的,Monitor 对象是同步的基本实现单元。
-
发展历程:
- JDK6之前,Monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作。(@所以说效率低下嘛)
- 现代的(Oracle)JDK 中,JVM 对此进行了大刀阔斧地改进,提供了三种不同的 Monitor 实现,也就是常说的三种不同的锁:偏斜锁(Biased Locking)、轻量级锁和重量级锁大大改进了其性能。
-
偏斜锁:JVM 会利用 CAS 操作(compare and swap),在对象头上的 Mark Word 部分设置线程 ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁
-
轻量级锁:
- 如果有另外的线程试图锁定某个已经被偏斜过的对象,JVM 就需要撤销(revoke)偏斜锁,并切换到轻量级锁实现。轻量级锁依赖 CAS 操作 Mark Word 来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。
- 因为重量级锁性能差,所以轻量级锁又衍生出了一种锁:自旋锁,其实现就是自循环若干次,通过CAS操作MARK WROD试图获取锁
其他锁模型
详细参考:王宝令极客时间上的课程《Java并发编程实战》- 08 | 管程:并发编程的万能钥匙
- 管程模型:
- Hasen模型:要求 notify() 放在代码的最后,这样 T2 通知完 T1 后,T2 就结束了,然后 T1 再执行,这样就能保证同一时刻只有一个线程执行。
- Hoare模型:T2 通知完 T1 后,T2 阻塞,T1 马上执行;等 T1 执行完,再唤醒 T2,也能保证同一时刻只有一个线程执行。但是相比 Hasen 模型,T2 多了一次阻塞唤醒操作。
- MESA模型(JAVA参考实现):MESA 管程里面,T2 通知完 T1 后,T2 还是会接着执行,T1 并不立即执行,仅仅是从条件变量的等待队列进到入口等待队列里面。这样做的好处是 notify() 不用放到代码的最后,T2 也没有多余的阻塞唤醒操作。但是也有个副作用,就是当 T1 再次执行的时候,可能曾经满足的条件,现在已经不满足了,所以需要以循环方式检验条件变量。—也就是产生假唤醒
锁变化
升级/膨胀
其实就是偏斜锁=》轻量级锁=》重量级锁的过程,见第一节 #底层实现
锁降级
锁降级确实是会发生的,当 JVM 进入安全点(SafePoint)的时候,会检查是否有闲置的 Monitor,然后试图进行降级。
synchronized锁的范围
- 范围
- 代码块
- 方法
- 对象
- 类
- 对象锁和类
- 类锁和对象锁是分开的,(现在只是个概念,用来区分对象锁的,是指静态方法的锁),程序中获得类锁的同时也可以获得对象锁。
- 同一个类锁和同一个类锁是互斥的,同一个对象锁和同一个对象锁互斥。 非静态方法不受类锁的影响
- 对象锁与实例对象相关, 不同的对象的对象锁不一样,可以同时获取两个不同对象的对象锁
package com.keven;
//类锁和对象锁的测试代码
public class SyncTest {
public static void main(String[] args) throws Exception {
runObjectLockTest();
System.out.println("finished runObjectLockTest");
runClassLockTest();
System.out.println("finished runClassLockTest");
runClassObjectLockTest();
System.out.println("finished runClassObjectLockTest");
Thread.sleep(10000);
}