1.什么是Synchronied
Synchronized是一个同步关键字,解决多个线程对共享资源的同步性问题,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
1.1 修饰目标
修饰目标 | 锁 | |
---|---|---|
方法 | 实例方法 | 当前实例对象(即方法调用者) |
静态方法 | 类对象 | |
代码块 | this | 当前实例对象(即方法调用者) |
class对象 | 类对象 | |
任意Object对象 | 任意示例对象 |
1.2 特点
- 可重入性(cpp源代码提供保障)
- 当代码段执行结束或出现异常后会自动释放对监视器的锁定
- 是非公平锁,在等待获取锁的过程中不可被中断
- synchronized的内存语义(详见面试打怪升升级-被问烂的volatile关键字,这次我要搞懂它(深入到操作系统层面理解,超多图片示意图))
- 互斥性,被synchronized修饰的方法同时只能由一个线程执行
1.3 实现原理
我们写个demo看下,使用javap命令,查看JVM底层是怎么实现synchronized
public class TestSynMethod1 {
synchronized void hello() {
}
public static void main(String[] args) {
String anything = "anything";
synchronized (anything) {
System.out.println("hello word");
}
}
}
同步块的jvm实现,可以看到它通过monitorenter
和monitorexit
实现锁的获取和释放。通过图片中的注解可以很好的解释synchronized的特性2,当代码段执行结束或出现异常后会自动释放对监视器的锁定。
注意,如果synchronized在方法上,那就没有上面两个指令,取而代之的是有一个ACC_SYNCHRONIZED修饰,表示方法加锁了。然后可以在常量池中获取到锁对象,实际实现原理和同步块一致,后面也会验证这一点
2.锁升级过程(锁的优化)
JDK1.6之前都是重量级锁
锁的4中状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态(级别从低到高)
2.1 偏向锁–>轻量级锁
为什么要引入偏向锁?
因为经过HotSpot的作者大量的研究发现,大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。
偏向锁的升级
当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。
总结
没有竞争的时候,就是偏向锁
2.2 轻量级锁–>重量级锁
为什么要引入轻量级锁?
轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。
轻量级锁什么时候升级为重量级锁?
线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;
如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。
但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。
注意
为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。一句话就是锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态。