
🔥个人主页: 寻星探路
🎬作者简介:Java研发方向学习者
📖个人专栏:、《
⭐️人生格言:没有人生来就会编程,但我生来倔强!!!
目录
[2、synchronized 实现原理是什么?](#2、synchronized 实现原理是什么?)
一、基本特点
结合上面的锁策略,我们就可以总结出,synchronized具有以下特性(只考虑JDK1.8):
1)开始时是乐观锁,如果锁冲突频繁,就转换为悲观锁。
2)开始是轻量级锁实现,如果锁被持有的时间较长,就转换成重量级锁。
3)实现轻量级锁的时候大概率用到的自旋锁策略。
4)是一种不公平锁。
5)是一种可重入锁。
6)不是读写锁。
二、加锁工作过程
JVM 将 synchronized 锁分为无锁、偏向锁、轻量级锁、重量级锁状态。会根据情况,进行依次升级。

1、偏向锁
第一个尝试加锁的线程,优先进入偏向锁状态。
偏向锁不是真的"加锁",只是给对象头中做一个"偏向锁的标记",记录这个锁属于哪个线程。
如果后续没有其他线程来竞争该锁,那么就不用进行其他同步操作了(避免了加锁解锁的开销)
如果后续有其他线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了,很容易识别当前申请锁的线程是不是之前记录的线程),那就取消原来的偏向锁状态,进入一般的轻量级锁状态。
偏向锁本质上相当于"延迟加锁",能不加锁就不加锁,尽量来避免不必要的加锁开销。
但是该做的标记还是得做的,否则无法区分何时需要真正加锁。
举个例子理解偏向锁:
假设男主是一个锁,女主是一个线程,如果只有这一个线程来使用这个锁,那么男主女主即使不领证结婚(避免了高成本操作),也可以一直幸福的生活下去。
但是女配出现了,也尝试竞争男主,此时不管领证结婚这个操作成本多高,女主也势必要把这个动作完成了,让女配死心。
2、轻量级锁
随着其他线程进入竞争,偏向锁状态被消除,进入轻量级锁状态(自适应的自旋锁)。
此处的轻量级锁就是通过CAS来实现。
通过CAS检查并更新一块内存(比如null=>该线程引用)。
如果更新成功,则认为加锁成功。
如果更新失败,则认为锁被占用,继续自旋式的等待(并不放弃CPU)。
自旋操作是一直让CPU空转,比较浪费CPU资源。
因此此处的自旋不会一直持续进行,而是达到一定的时间/重试次数,就不再自旋了。
也就是所谓的"自适应"。
3、重量级锁
如果竞争进一步激烈,自旋不能快速获取到锁状态,就会膨胀为重量级锁
此处的重量级锁就是指用到内核提供的mutex。
执行加锁操作,先进入内核态。
在内核态判定当前锁是否已经被占用。
如果该锁没有占用,则加锁成功,并切换回用户态。
如果该锁被占用,则加锁失败。此时线程进入锁的等待队列,挂起,等待被操作系统唤醒。
经历了一系列的沧海桑田,这个锁被其他线程释放了,操作系统也想起了这个挂起的线程,于是唤醒这个线程,尝试重新获取锁。
三、其他的优化操作
1、锁消除
编译器+JVM判断锁是否可消除,如果可以,就直接消除。
什么是"锁消除"?
有些应用程序的代码中,用到了synchronized,但其实没有在多线程环境下。(例如StringBuffer)
java
StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d");
此时每个append的调用都会涉及加锁和解锁,但如果只是在单线程中执行这个代码,那么这些加锁解锁操作是没有必要的,白白浪费了一些资源开销。
2、锁粗化
一段逻辑中如果出现多次加锁解锁,编译器+JVM会自动进行锁的粗化。
锁的粒度:粗和细

实际开发过程中,使用细粒度锁,是期望释放锁的时候其他线程能使用锁。
但是实际上可能并没有其他线程来抢占这个锁,这种情况JVM就会自动把锁粗化,避免频繁申请释放锁。
举个例子理解锁粗化:
滑稽老哥当了领导,给下属交代工作任务:
方式一:
打电话,交代任务1,挂电话。
打电话,交代任务2,挂电话。
打电话,交代任务3,挂电话。
方式二:
打电话,交代任务1,任务2,任务3,挂电话。
显然,方式⼆是更高效的方案。
可以看到,synchronized的策略是比价复杂的,在背后做了很多事情,目的为了让程序猿哪怕啥都不懂,也不至于写出特别慢的程序。
JVM开发者为了Java程序猿操碎了心。

四、相关面试题
1、什么是偏向锁?
偏向锁不是真的加锁,而只是在锁的对象头中记录一个标记(记录该锁所属的线程)。如果没有其他线程参与竞争锁,那么就不会真正执行加锁操作,从而降低程序开销,一旦真的涉及到其他的线程竞争,再取消偏向锁状态,进入轻量级锁状态。
2、synchronized 实现原理是什么?
参考上面的 synchronized 原理章节全部内容