前言我们在回顾synchronized的时候,和lock相比,总说它是重量级的锁,性能差,不如lock方便,但其实synchronized是一个老牌关键字在Java中,很多jdk原始代码中都用到了它,所以Java开发团队还是很喜欢这个“私生子”的,所以为了减轻它的重量,早在Java6的时候,开发者还专门将它设计为synchronized。一套优化的加工流程。1、方法中加上synchronized时,锁的是this对象,防止多个线程同时访问this对象的synchronized方法;当加在静态方法上时,锁是该类的类对象,防止多个线程同时访问该类的synchronized方法,适用于该类型的所有对象;添加到代码块时,锁是代码块所在的对象,防止多个线程同时访问代码块;2、JVM实现synchronized代码块同步:使用monitorenter和monitorexit指令实现的同步方式:ACC_SYNCHRONIZED修饰3、Java对象头(存储锁类型)解锁升级前,必须先知道Java对象头,记录的信息官方对象头中实现锁升级过程中的线程判断。Java对象:Java对象头:包含两部分:MarkWord和类型指针MarkWord:MarkWord用于存放对象本身的运行时数据,如HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏线程身份证等占用内存大小与虚拟机的位长一致(32位JVM->MarkWord为32位,64位JVM->MarkWord为64位)。自旋锁,自适应自旋)->重量级锁各种锁状态下markword的内容:statebiasedlockbitflagstoragecontentunlocked001objecthashcode,objectgenerationagebiasedlock101biasedthreadID,Biasedtimestamp,objectgenerationage轻量级锁000指向锁记录扩展的指针(重量级锁)010执行重量级锁指针GC标记011清空(不需要记录信息)无锁状态->偏向锁优先当处于无锁状态时,MarkWord存储这样的信息作为对象的哈希码,偏向锁为0。此时线程1进入,发现没有记录线程ID信息,于是直接CAS操作将自己的线程ID设置进去,设置为成功的。如果获得了偏向锁,则将偏向锁标志改为1;如果失败,将升级为轻量级锁;线程1执行完毕后,将markWord中的线程ID置空,即释放偏向锁;如果在线程1释放之前,此时线程2进入时,先检查markWord中记录的线程ID是否是自己的(这也是偏向锁的适用场景:经验表明,大多数情况下,同一个线程进入同一个同步代码块),如果是,则获取锁成功;如果没有,说明已经偏向其他线程,也就是发生了线程竞争。这个时候一般情况下偏向锁是不能重偏的,所以需要升级。如果可以重偏的话,会尝试CAS操作替换markWord中的threadid。成功则偏向线程2,失败则升级锁。偏向锁升级前,需要先撤销偏向锁。如果此时某个线程已经被偏,则需要根据markWord中记录的线程id来判断该线程是否存活。如果不存活,则进入无锁状态或重偏状态。将markWord复制到线程栈,开始升级偏向锁->当两个或多个线程竞争轻量级锁时(已经偏向一个线程,此时出现另一个线程竞争,即升级),开始upgrade对于轻型锁。加锁时,线程会在自己的栈帧中创建一个LockRecord锁记录(会把对象原来的markWord内容复制为一个lockRecord,包括对象生成年龄等信息,原来的markWord只会有一个指针),同时,CAS将锁对象的markWord替换为指向lockRrecord的指针。如果操作成功,则获得轻量级锁;当线程执行解锁时,只需要移除栈帧中的lockRecord,无需修改markWord中的内容。轻量级锁->重量级锁设置失败,证明存在竞争,此时会进入自旋状态。线程自旋次数超过10次,或自旋线程数超过CPU核数的一半。(JDK1.6之后,增加了自适应自旋AdaptiveSelfSpinning,由JVM自己控制)。它将升级为重量级锁。升级重量级锁:向操作系统申请资源,CPU从3级调用到0级,线程挂起,进入等待队列,等待操作系统的调度,再映射回用户空间。因此,升级为重量级锁后,用户线程被系统线程挂起,需要从用户态转换到内核态。这也是为什么一开始synchronized很重,性能很差的原因!总结:同步锁升级过程是单向的,不可逆的;升级过程围绕锁对象中markWord内容的变化展开;每种锁适用于不同的场景,比如偏向锁,适用于同一个线程多次进入同步代码的情况。定量锁适用于竞争较小,同步执行时间较短的情况(自旋一段时间后即可获得锁)。顺便说一下,synchronized是可重入的。这不仅从偏向锁可以看出来,升级到重量级锁也可以看出来。,也支持
