竹笋

首页 » 问答 » 灌水 » 并发编程synchronized的偏
TUhjnbcbe - 2023/7/9 21:04:00

内存布局对应对应的锁状态

先说锁状态的变化结论

跟踪锁状态需要查看内存布局:不熟悉的可阅读这篇文章

java对象的内存布局

偏向锁

偏向锁是一种针对加锁操作的优化手段。

在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了消除数据在无竞争情况下锁重入(CAS操作)的开销而引入偏向锁。

对于没有锁竞争的场合,偏向锁有很好的优化效果。

JVM启用了偏向锁模式:jdk6之后默认开启

新创建对象的MarkWord中的ThreadId为0,说明此时处于可偏向但未偏向任何线程,也叫做匿名偏向状态(anonymouslybiased)。

偏向锁延迟偏向

HotSpot虚拟机在启动后开启偏向锁模式默认在4s后。

为了减少初始化时间,JVM默认延时加载偏向锁。

//关闭延迟开启偏向锁-XX:BiasedLockingStartupDelay=0//禁止偏向锁-XX:-UseBiasedLocking

上图的代码可以验证:从无锁变为偏向锁(4秒)

偏向锁在无竞争的时候一直是偏向锁

publicstaticvoidmain(String[]args)throwsInterruptedException{log.debug(Thread.currentThread().getName()+"最开始的状态。。。\n"+ClassLayout.parseInstance(newObject()).toPrintable());//HotSpot虚拟机在启动后有个4s的延迟才会对每个新建的对象开启偏向锁模式Thread.sleep();Objectobj=newObject();newThread(newRunnable(){

Overridepublicvoidrun(){log.debug(Thread.currentThread().getName()+"开始执行准备获取锁。。。\n"+ClassLayout.parseInstance(obj).toPrintable());synchronized(obj){log.debug(Thread.currentThread().getName()+"获取锁执行中。。。\n"+ClassLayout.parseInstance(obj).toPrintable());}log.debug(Thread.currentThread().getName()+"释放锁。。。\n"+ClassLayout.parseInstance(obj).toPrintable());}},"thread1").start();Thread.sleep();log.debug(Thread.currentThread().getName()+"结束状态。。。\n"+ClassLayout.parseInstance(obj).toPrintable());}

执行结果

从结果可以看出:无锁状态经过4秒变为偏向锁,之后的的状态一直是偏向锁!

在进入同步代码块后,锁的偏向线程由0变为具体的线程。

在同步代码块外调用hashCode()方法

进入同步代码块后锁升级为轻量级锁

当对象可偏向(线程ID为0)时,MarkWord将变成未锁定状态,并只能升级成轻量锁。

在同步代码块内调用hashCode()方法

直接升级为重量级锁

当对象正处于偏向锁时,调用HashCode将使偏向锁强制升级成重量锁。

偏向锁撤销:自己验证wait和notify

调用锁对象的obj.hashCode()或System.identityHashCode(obj)方法会导致该对象的偏向锁被撤销。

因为对于一个对象,其HashCode只会生成一次并保存,偏向锁是没有地方保存hashcode的。

轻量级锁会在锁记录中记录hashCode。

重量级锁会在Monitor中记录hashCode。

当对象可偏向(线程ID为0)时,MarkWord将变成未锁定状态,并只能升级成轻量锁。

当对象正处于偏向锁时,调用HashCode将使偏向锁强制升级成重量锁。

偏向锁状态执行obj.notify()会升级为轻量级锁。

调用obj.wait(timeout)会升级为重量级锁。

轻量级锁

倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段,此时MarkWord的结构也变为轻量级锁的结构。

轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间多个线程访问同一把锁的场合,就会导致轻量级锁膨胀为重量级锁。

轻量级锁在降级的时候直接变为无锁状态!(查看之前在同步代码块外调用hashCode()方法)

模拟竞争不激烈的场景

Slf4jpublicclassTestMemory{publicstaticvoidmain(String[]args)throwsInterruptedException{log.debug(Thread.currentThread().getName()+"最开始的状态。。。\n"+ClassLayout.parseInstance(newObject()).toPrintable());//HotSpot虚拟机在启动后有个4s的延迟才会对每个新建的对象开启偏向锁模式Thread.sleep();Objectobj=newObject();Threadthread1=newThread(newRunnable(){

Overridepublicvoidrun(){log.debug(Thread.currentThread().getName()+"开始执行thread1。。。\n"+ClassLayout.parseInstance(obj).toPrintable());synchronized(obj){log.debug(Thread.currentThread().getName()+"获取锁执行中thread1。。。\n"+ClassLayout.parseInstance(obj).toPrintable());}log.debug(Thread.currentThread().getName()+"释放锁thread1。。。\n"+ClassLayout.parseInstance(obj).toPrintable());}},"thread1");thread1.start();//控制线程竞争时机Thread.sleep(1);Threadthread2=newThread(newRunnable(){

Overridepublicvoidrun(){log.debug(Thread.currentThread().getName()+"开始执行thread2。。。\n"+ClassLayout.parseInstance(obj).toPrintable());synchronized(obj){log.debug(Thread.currentThread().getName()+"获取锁执行中thread2。。。\n"+ClassLayout.parseInstance(obj).toPrintable());}log.debug(Thread.currentThread().getName()+"释放锁thread2。。。\n"+ClassLayout.parseInstance(obj).toPrintable());}},"thread2");thread2.start();Thread.sleep();log.debug(Thread.currentThread().getName()+"结束状态。。。\n"+ClassLayout.parseInstance(obj).toPrintable());}}

竞争不激烈的场景的运行结果

重量级锁

轻量级锁经过一次自选如果没有获取到锁,直接膨胀为重量级锁。

重量级锁是基于Monitor机制,并且在Monitor中记录hashCode

模拟竞争激烈的场景

去掉不激烈的场景中的以下代码就是竞争激烈的场景

//控制线程竞争时机Thread.sleep(1);

竞争激烈的场景的运行结果

建议继续阅读

「并发编程」synchronized底层原理:Monitor(管程/监视器)

「并发编程」从一个程序入门synchronized

「并发编程」原子操作的基础:CAS

「并发编程」深入简出的带你精通java线程

1
查看完整版本: 并发编程synchronized的偏