java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java synchronized偏向锁

Java synchronized偏向锁的概念与使用

作者:每天都要进步一点点

因为在我们写的程序当中可能会经常使用到synchronized关键字,因此JVM对synchronized做出了很多优化,而在本篇文章当中我们将仔细介绍JVM对synchronized的偏向锁的细节

一、什么是偏向锁

HotSpot作者经过研究实践发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。

偏向锁的“偏”,它的意思是锁会偏向于第一个获得它的线程,会在对象头(Mark Word中)存储锁偏向的线程ID,以后该线程进入和退出同步块时只需要检查是否为偏向锁、锁标志位以及ThreadID即可。

如下图是偏向锁对象头MarkWord布局:

二、偏向锁原理

偏向锁总是被第一个占用它的线程拥有,这个线程就是锁的偏向线程。在偏向锁的MarkWord中,就有一块区域用来记录偏向线程的ID,这个是在锁第一次被拥有的时候记录的。

因为记录了偏向线程ID,那么后续如果这个偏向线程进入和退出同步代码块的时候,就不需要再次加锁和解锁。

偏向锁再次进入同步代码块,是如何保证不需要再次加锁的?

偏向锁的线程会检查锁对象的MarkWord中是不是存放着自己的线程ID。

相等

表示偏向锁现在就是偏向当前线程的,无需再尝试获得锁。以后每次同步,都会检查锁的偏向线程ID与当前线程ID是否一致,一致就直接进入同步,省去了CAS去更新对象头的操作,提高了锁的性能。

不相等

表示偏向锁现在不是偏向当前线程的,此时发生了竞争,这时候当前线程就会尝试CAS去更新锁对象MarkWord中线程ID,尝试指定为自己的线程。此时也有两种情况:

1)、更新成功:表示已经将锁对象MarkWord中的线程ID替换为自己的线程ID,之前的线程可能刚好运行完,这时候锁重新偏向为当前线程,这种情况下,不会发生锁升级,锁仍然为偏向锁,只是偏向线程换成了另外一个线程;

2)、更新失败:表示之前的线程还在持有锁,那么当前线程只能一直CAS,当达到一定次数后,如果还没CAS成功,那么就会发生【偏向锁 -> 轻量级锁】的锁升级过程。

注意,偏向锁只有遇到其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁的。

三、偏向锁演示

public class BiasedLockDemo01 {
    public static void main(String[] args) {
        Object objLock = new Object();
        new Thread(() -> {
            synchronized (objLock) {
                System.out.println(ClassLayout.parseInstance(objLock).toPrintable());
            }
        }, "t1").start();
    }
}

如上我们看到,markword的倒数三位是000,根据前面的图,000表示的是轻量级锁,此时只有一个线程访问,为什么输出来的不是偏向锁标识101呢?

原因其实是偏向锁在Java 6之后是默认启用的,但在应用程序启动几秒钟(默认延迟4秒)之后才会激活,可以使用 -XX:BiasedLockingStartupDelay=0参数关闭延迟,让其在程序启动时立刻启动。当然为了演示,也可以在程序中休眠5秒,等待偏向锁激活后。

下面我们添加运行时JVM参数,再次启动程序,观察内存布局:

可以看到关闭偏向锁延迟后,当前锁就是偏向锁了。

四、偏向锁的处理流程

当线程第一次访问同步块并获取锁时,偏向锁处理流程如下:

1)、虚拟机将会把对象头中的是否偏向锁标志位设为“1”,即偏向模式;

2)、使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中 ,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作,效率极高;

五、偏向锁的撤销

前面提到,大部分情况下,锁都是被同一个线程获取到,持有偏向锁的线程不会主动释放锁。那么大部分情况下,不会涉及到偏向锁的撤销,当有另外的线程尝试竞争偏向锁的时候,这个时候才会涉及偏向锁的撤销流程。

举个例子,有线程A、线程B两个线程竞争获取锁,锁大部分情况下都被线程A持有,此时锁偏向于线程A,这样线程A每次进入/退出同步代码块,都无需再次加锁,只需判断MarkWord中保存的线程ID是否等于自己的线程ID。

线程A不会主动释放偏向锁,当运行了一段时间后,突然线程B过来尝试竞争这个偏向锁了,此时持有偏向锁的线程A会发生偏向锁的撤销。

偏向锁的撤销需要等待全局安全点(这个时间点没有代码正在执行),同时会检查持有偏向锁的线程A是否还在执行:

1)、线程A还是同步代码块中运行:发生【偏向锁 -> 轻量级锁】的升级过程。此时轻量级锁由原持有偏向锁的线程A持有,继续执行其同步代码,而正在竞争的线程B会在外面CAS自旋等待获取这个轻量级锁。

2)、线程A刚好执行完成同步代码块:则会将对象头MarkWord设置为无锁状态并撤销偏向锁,然后锁重新偏向到线程B,注意,这里会修改MarkWord中线程ID保存为线程B的线程ID。

流程图大体如下:

六、偏向锁的好处

偏向锁是在只有一个线程执行同步块时进一步提高性能,适用于一个线程反复获得同一个锁的情况。偏向锁可以提高带有同步但无竞争的程序性能。它并不一定总是对程序运行有利,如果程序中大多数的锁总是被多个不同的线程访问比如线程池,那偏向模式就是多余的,反而会影响效率。

注意,在JDK15中,已经废弃偏向锁。目前还是使用JDK1.8居多,所以我们还是有必要了解一下偏向锁。

到此这篇关于Java synchronized偏向锁的概念与使用的文章就介绍到这了,更多相关Java synchronized偏向锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文