文章目录
- 1. Synchronized 的优化操作
- 1.1 偏向锁
- 1.2 轻量级锁(自旋锁)
- 1.3 重量级锁
- 2. 其他的优化操作
- 2.1 锁消除
- 2.2 锁粗化
- 3. 相关面试题
1. Synchronized 的优化操作
两个线程针对同一个对象加锁,就会产生阻塞等待。
Synchronized 内部其实还有一些优化机制,存在的目的就是为了让这个锁更加的高效好用。
JVM 将 synchronized 锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态。
会根据情况,进行依次升级。
1.1 偏向锁
进行加锁的时候,首先会进行到 偏向锁 的状态。
偏心锁并不是真正的加锁,而只是占个位置。有需要的时候再加速,没需要就不加了。
synchronized 加锁的时候,并不是真正的加锁,而是先是 偏向锁 状态,做个标记。
(这个过程非常轻量)如果整个使用锁的过程中,都没有出现锁竞争。在 synchronized 执行完之后
取消偏向锁即可。
但是,如果使用过程中,另一个也尝试加锁,在它加锁之前,迅速把偏向锁升级成真正的加锁状态,
另一个线程也就只能阻塞等待了。
就好比我和一个一个女生搞暧昧,但是又不和她确定关系。
当有一天,突然出现了另外一个男人,比我优秀还比我帅,我担心她被抢走就赶紧和她确定男女朋友关系,然后让她和之前的那个男人彻底断绝来往。
1.2 轻量级锁(自旋锁)
当 synchronized 发生锁竞争的时候,就会从 偏向锁 升级成 轻量级锁
此时,synchronized 相当于是通过自旋的方式来进行加锁的。(和 CAS 篇章伪代码一致)
CAS 文章:
https://blog.csdn.net/m0_63033419/article/details/128614745?spm=1001.2014.3001.5501
自旋操作是一直让 CPU 空转,比较浪费 CPU 资源。
因此此处的自旋不会一直持续进行,而是达到一定的时间/重试次数,就不再自旋了。
也就是所谓的 “自适应”。
如果要是很快锁被释放了,自旋还是很划算的,但是如果迟迟拿不到锁,一直自旋,那就不划算了。
synchronized 自旋不是无休止的自旋,自旋到一定程度之后就会再次升级为重量级锁。(挂机等待锁)
1.3 重量级锁
重量级锁 则是基于操作系统原生的 API 来进行加锁的。Linux 则提供了 mutex 一组 API 。
操作系统内核提供的加锁功能,这个锁会影响到线程的调度。
此时如果线程进行了重量级锁的加锁,并且发生锁竞争,此时线程就会被放到阻塞队列中,
暂时不参与 CPU 调度了。然后直到锁被释放了,这个线程才有机会被调度到并且有机获取到锁。
需要注意的是锁只能升级,不能降级。
除非是另外的一个锁对象,但是还是会重复上面的升级过程。
2. 其他的优化操作
2.1 锁消除
有些应用程序的代码中,用到了 synchronized,但其实没有在多线程环境下。(例如 StringBuffer)
StringBuffer 里的关键方法多带有 synchronized ,但是如果在单线程中使用 StringBuffer ,
加了 synchronized 锁也是白加,此时编译器就会直接把这些加锁操作给消除了。
2.2 锁粗化
指的是锁的粒度,synchronized 包含的代码越多,粒度就越粗;包含的代码越少,粒度就越细。
通常情况下,锁的粒度细一点比较好。加锁部分的代码是不能并发执行的,锁的粒度越细,能并发的代码就越多;反之就越少。
但是有些的情况下,锁的粒度粗一些会更好。
如果两次加锁之间的间隙非常小,此时还不如直接一次搞一把大锁直接搞定了。
3. 相关面试题
1、什么是偏向锁?
偏向锁不是真的加锁,而只是在锁的对象头中记录一个标记(记录该锁所属的线程)。
如果没有其他线程参与竞争锁,那么就不会真正执行加锁操作,从而降低程序开销。
一旦真的涉及到其他的线程竞争,再取消偏向锁状态,进入轻量级锁状态。
2、synchronized 实现原理 是什么?
参考上面的 synchronized 原理的全部内容。