自旋锁和自适应自旋锁
为何引入
互斥同步最大的性能消耗在于阻塞的实现。即线程的挂起和唤醒必须转入内核态中实现,线程的切换即内核态和用户态的切换给并发性能带来了很大的消耗。
针对场景
假设某个线程所持有互斥锁的时间很短。
这样我们可以让等待锁的线程不进行休眠,而进行自旋(比如循环100次),这样的做法避免了线程的切换。
如果某些线程锁持有锁的时间很长这样做就不行了。
自旋锁的优化--自适应自旋锁
即优化每次自旋的时间。我们可以根据上一次这个互斥锁等待的时间以及线程的状态等信息,来决定此次是否自旋以及自旋的时间。这样自旋的时间就变成动态的了。
额外
自旋锁自1.6开始后默认就是开启的。
锁清除
为何引入
在一些代码中加入了互斥同步锁,但实际上并不可能存在同步现象。我们需要将这些互斥同步锁清除掉。
针对场景
锁清除现象发生在即时编译器运行时。
如何判定代码中的互斥同步锁是多余的呢?
主要依据:逃逸技术。--即在一段代码中,发现堆中的所有数据都不会逃逸出去而被其他线程访问,则就可认为它们是线程私有的。
锁粗化
为何引入
原则上我们是希望加锁的代码块越小越好。但如果一段代码中频繁地对某个对象进行加锁和解锁。这样也会导致消耗性能。
针对场景
当虚拟机检测到一段代码频繁地对同一个对象进行加锁和解锁时,就会将锁范围扩大到这段代码之外,进行一次加锁和解锁就好了。
轻量锁
针对场景
对于大部分的锁,在整个同步周期内都不存在竞争。
--这胡话的意思是:一段代码在被某个线程上锁到解锁的过程中不会存在其他线程来欲访问这段代码。
具体优化
- 加锁:当前线程执行CAS操作。如果成功则继续执行同步代码,失败说明有其他线程占有。如果有两个线程争用同一个锁,则轻量锁不再生效,膨胀为重量级锁。
- 解锁:也是通过使用CAS操作来完成。
这里说一下:
- 轻量锁是可以膨胀为重量级锁的
- 轻量锁之所以高效,是因为加锁和解锁只使用了CAS操作而不是使用系统互斥量。两者的性能差别很大。
- 在有互斥锁的竞争下,轻量锁的效率很低。因为除了使用互斥量外还额外加了CAS操作。
- 具体的CAS操作这里就不多说了。
偏向锁
针对场景
- 偏向锁的“偏”是指偏向第一个获得锁的线程,如果接下来的过程中,没有其他线程获得锁,则第一个线程将永远无需做同步操作。
- 轻量锁是在无竞争状态在使用CAS来代替互斥量,而偏向锁是在无竞争状态在把整个同步都消除掉,连CAS都省了。
- 既然无需CAS操作,是不是启用一定会提高效率。其实并不是,如果锁总是被不同线程访问,那么偏向锁就是多余的。
- 如果偏向锁无效之后,可以膨胀为轻量锁。
总结
- 自旋锁、锁清除、锁粗化都是默认开启的,他们的存在会提高效率。
- 偏向锁和轻量锁是在特殊的场景下,才可以提高性能。否则还会降低性能。
- 偏向锁可以膨胀为轻量锁,轻量锁可以膨胀为重量级锁。不可反向膨胀。
网友评论