内存布局对应对应的锁状态
先说锁状态的变化结论
跟踪锁状态需要查看内存布局:不熟悉的可阅读这篇文章
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线程