Synchronized原理
1.基本特点
Synchronized具有以下特点(只考虑JDK1.8):
1. 开始是乐观锁,如果锁发生冲突,就转化悲观锁;
2.开始是轻量级锁,如果锁被持有的时间比较长,就会转化成重量级锁;
3.实现轻量级锁的时候大概率用到了自旋锁策略;
4.是一种不公平锁。
5.是一种可重入锁。
6.不是读写锁。
2.加锁工作过程
JVM将synchronized锁分为 无锁,偏向锁,轻量级锁,重量级锁 状态。会根据情况,进行升级。
2.1偏向锁
第一次线程尝试加锁,优先进入偏向锁状态
偏向锁不是真的“加锁”,只是给对象头中做一个标记“偏向锁的标记”,记录这个锁属于哪一个线程。
如果后续没有其他线程来竞争该锁,那么就不用进行其他同步操作了(避免了加锁和解锁的开销)
如果后续有线程来竞争这个锁,那就取消偏向锁状态,进入一般的轻量锁状态。
偏向锁的本质是能不加锁,就不加锁,如果其他线程竞争锁,偏向锁提前一步转换为轻量级锁,对线程进行加锁。
【通俗的栗子】偏向锁就像男女两人谈恋爱,没有官宣,此时就省去了被双方家长反复询问的麻烦。如果此时有第三者插足,原来的男女就会立即的官宣,第三者只能等待。
2.2轻量级锁
随着其他线程的竞争,偏向锁状态被消除,进入轻量级锁状态(自适应的自旋锁)
此处的轻量级锁就是通过CAS来实现。
- 通过CAS检查并更新一块内存;
- 如果更新成功,则认为加锁成功;
- 如果更新失败,则认为锁被占用,继续自旋式的等待(不会放弃CPU)
自旋操作是一直让CPU空转,比较浪费CPU的资源
但是此处的自旋锁不会一直的持续进行,而是到达一定的时间/重试次数后,不再自旋了。
也就是所谓的“自适应”
2.3重量级锁
如果锁竞争进一步加剧,自旋锁不能快速的获取到锁的状态,就会膨胀为重量级锁。
此处的重量级锁就是指用到内核提供的mutex
- 执行加锁操作,先进入内核态;
- 在内核态判定当前锁是否已经被占用;
- 如果该锁没有占用,则加锁成功,并切换会用户态;
- 如果该锁被占用,则加锁失败,此时线程进入锁等待队列,等待被系统唤醒;
- 经历了一系列的沧海桑田,这个锁被其他线程释放了,操作系统也想来了这个挂起等待的线程,于是唤醒这个线程,尝试重新获取锁。
3.其他的优化
3.1锁消除
编译器+JVM会判断锁是否可以被消除,如果可以就直接消除。
什么是“锁消除”?
比如在单线程的环境下,我们仍旧使用synchronized或者使用带有锁的类(如StringBuffer),那么就会进行锁消除的操作。
3.2锁粗化
一段代码中如果出现多次对使用同一把锁进行加锁和解锁的操作,编译器+JVM会自动进行锁的粗化
在实际开发构成中,我们使用细粒度锁,是希望释放锁后其他线程获得锁并执行, 但可能没有线程抢占这个锁。这种情况下,JVM就会自动把锁粗化,避免频繁的申请锁和释放锁。
4.相关面试题
1)什么是偏向锁?
偏向锁不是真正的加锁,而只是在锁的对象头中记录一个标记(记录该锁获取所属的线程)。如果没有其他线程参与竞争锁,那么就不会真正的进行加锁操作,从而降低系统的开销。一旦有其他线程试图加锁,那么就取消偏向锁,进入轻量级锁状态。
2)synchronized的实现原理是什么?