java 对象头 有一个部分叫做MarkWord 占32 位或者64位。用来表示对象的状态 倒数第三位为1 且 最后两位为01代表该对象处于可偏向状态。 所以当synchronized 关键字 锁定对象进行同步代码块时,首先检查对象是否可偏向,如果可以则通过cas设置MarkWord 的高位地址为该线程的地址,成功之后 之后该线程再次获取锁对象的时候,检查其高位线程id为自己则直接进入同步代码块。对于轻量级锁 是线程栈帧中生成一个Lock Record (复制锁对象的Mark word + owner 指向锁对象),再通过cas锁对象的高位指向栈帧中的锁记录。
偏向锁的使用场景:
只有一个线程在使用,只是在第一次偏向的时候通过cas 设置锁对象的MarkWord 高位地址为偏向线程id,之后该线程在synchronized 锁对象的时候 只需要判断是否是偏向自己的线程id,若是 则可以直接进入同步代码块。
轻量级锁使用场景:
大多数同步代码并不存在真正的并行执行,即真正的一个线程在执行同步代码的时候另一个线程恰好也需要进入同步代码。大部分时间是两个线程交替进行同步代码的执行,这时候就需要使用轻量级锁。轻量级锁 的设置过程:synchronized 获取锁对象的时候,如果是无锁状态或者是不可偏向状态,则首先在当前线程栈帧中生成一个锁记录,锁记录一部分是复制锁对象的Markword 另一部分是锁引用指向锁对象的指针之后通过cas设置Markword高位为该线程锁记录地址,如果成功代表该线程拥有该锁(通过对象头Markword高位是否指向当前线程栈帧),如果失败则需要判断该线程是否已经拥有了该锁,若拥有则直接进入同步代码块执行,如果失败则代表可能存在两个线程争抢同一把锁的情况,也有可能偏向锁的线程已经消亡但是其偏向状态在同步代码块退出的时候并不会重置为未偏向 或者无锁状态,也有可能是未消亡但是仍然在执行非同步代码块。
重量级锁:
当出现两个线程真正同时想进入同步代码块,即争抢同一把锁,这时候就会升级为重量级锁。
java 中每一个对象都有一个与之关联的Monitor对象(ObjectMonitor) 该对象是jvm级别的c++ 实现,其中重要的数据结构变量有三个队列:ContentionList 和entryList 以及waitset 在这三个队列中的线程 都是处于阻塞状态。其实竞争队列和entrylist队列都是等待队列,那么为什么等待队列分为两个呢?若只有一个等待队列jvm 线程需要从队尾元素取一个节点 使其去竞争锁,所以在多个抢占锁的线程节点通过cas 设置尾结点时会很耗时,所以将contentionList 设置为可以并发访问的等待队列,而entrylist 只是存放有资格竞争锁的队列减少cas操作的时间。所有需要竞争锁的队列都需要放到竞争队列中,当有资格竞争锁的时候则去转移到entrylist 中。还有一个就是waitset 当线程调用wait 方法的时候就会将其放入等待队列中。
什么时候会膨胀为重量级锁
1、处于偏向锁 或者轻量级锁的锁对象调用hashcode 方法时候。由于偏向锁或者轻量级锁的markWord 字段无法保存hashcode 字段。但是重量级锁可以保存。
2、获得锁的线程调用了锁对象的wait方法。当调用wait方法时候需要将该线程封装成一个结点放在等待队列里。而等待队列需要管程模型即重量级锁才有的。
3、多个线程真正发生了同时想进入同步代码块,出现了竞争,那么应该升级为重量级锁。