无锁->偏向锁->轻量级锁->重量级锁

1,677 阅读4分钟

偏向锁

大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。

偏向锁会偏向于第一个获得它的线程,它会在对象头存储锁偏向的线程id,以后该线程进入和退出同步代码块时只需要检查是否为偏向锁、锁标志位、以及ThreadId即可。

一旦出现多个线程竞争时必须撤销偏向锁,所以撤销偏向锁的消耗的性能必须小于之前节省下来的CAS原子操作性能消耗,不然就得不偿失了。

偏向锁撤销

一旦有两个线程来竞争锁的时候,就会撤销偏向锁。

全局安全点,这个点所有的线程都会被停下来。

偏向锁原理

当锁对象第一次被线程获取的时候,虚拟机会把对象头中的标志位设为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程ID记录在对象的Mark Word中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作,偏向锁的效率高。

偏向锁的好处

偏向锁是在只有一个线程执行同步块时进一步提高性能,适用于一个线程反复获得同一把锁的情况。偏向锁可以提高带有同步但无竞争的程序性能。

轻量级锁

轻量级锁是相对于传统的monitor重量级锁而言的,轻量级锁不能用来代替重量级锁。它只是在一定的情况下减少消耗。

轻量级锁的目的:在多线程交替执行同步块的情况下,尽量避免重量级锁引起的性能消耗。但是如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀为重量级锁。

轻量级锁原理

当关闭偏向锁或者多个线程竞争偏向锁导致偏向锁升级为重量级锁,则会尝试获取轻量级锁,步骤如下:

原理:在当前栈帧中会创建Lock Record锁记录的一块空间,其中的displaced将对象头中的hashcode、分代年龄、锁标志保存进去,owner指向当前锁对象。对象头也会保存lock record的空间地址。

轻量级锁的释放

轻量级锁的好处

在多线程交替执行同步块的情况下,可以避免重量级锁引起的性能消耗。

自旋锁

原理:当多个线程竞争同步代码块时,线程可能对共享资源的操作时间是比较短的,也就是执行同步代码块花费的时间较短,如果我们让其他线程去阻塞等待,开销太大。

一个线程获取同步代码块,另一个线程在外面循环(自旋)尝试的获取锁,外面的线程就不会进入阻塞状态。

当自旋固定次数不为0时进入自旋,每次去减1.尝试一次是否能获取到锁,获取不到则花点时间等待一下,再进行下一次循环

适应性自旋

默认自旋次数为10,每次减1尝试一下是否获取到锁,如果获取到锁则去修改一下自旋时间,允许自旋的时间比以前长一些,因为这次抢到了意味着下次很可能抢到。如果没有抢到则进入自旋等待。

锁消除

锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是检测到不可能存在共享数据竞争的锁进行消除。如果判断一段代码中,堆上所有数据都不会逃逸出去从而被其他线程访问到,那就可以把他们当做堆上数据对待,认为他们是线程私有的,同步加锁自然就无需进行。

锁粗化

什么是锁粗化?JVM会探测到一连串细小的操作都使用同一个对象锁,将同步代码快的范围放大,放到这串操作的外面,这样只需要加一次锁即可。