1 偏向锁
1.1 偏向锁的特点
- 偏向锁在锁对象的markword后三位是101,而锁对象的后三位标志是001。偏向锁在markword的第一部分是指向的偏向线程id,而在锁对象中第一部分指向的是这个对象的hashcode
- 对象的hashcode()方法是一个懒加载的方法,即仅仅开始调用这个方法的时候,才会给锁对象markword的hashcode字段赋值,否则都是0;当成功写入hashcode的时候,此时这个锁对象就不能在作为轻量级锁进行使用了,可以这样理解,因为在轻量级锁中,偏向的线程id是需要写入到锁对象的markword中的,而因为hashcode已经被写入,因此没有多余空间可以写入偏向的线程id了
1.2 偏向锁的批量重偏向
因为一个对象锁默认都会偏向于当前的线程,当创建了多个锁对象的时候,相当于多个锁对象都偏向于当前的线程对象。当其他的线程来尝试获得这把锁的时候这个偏向锁就会进行撤销转为轻量级锁(这个具体过程后面再讲),当撤销的次数过多JVM会发出一个疑问,是不是偏向的线程错误了,因此把后面的锁对象的偏移线程全部重新偏向到另外的线程上。
1.3 偏向锁的批量撤销
在批量重偏向的基础上,如果还会发生多次撤销,那么此时JVM就会认为,偏向这个事情本身就是错误的。直接全部取消偏向。其实可以这么总结,在JVM的视角上,嗯,先设一个偏向状态把,让他们方便点;之后发生了多次撤销,JVM会想,可能是我让他们偏向的线程不对,剩下的全部重新偏向吧!后来又一直在撤销,JVM够了,全部都别偏向了,全部取消。
1.4 偏向锁至轻量级锁的转化
当一个锁对象已经偏向为了一个线程,当其他线程来获得这把锁的时候,会发现这个锁对象的markword指向的是另外一个线程,此时这个锁就会转换成普通的对象(markword的后三位是001),之后会进入轻量级锁的上锁流程。
2.轻量级锁
2.1 轻量级锁的上锁流程
轻量级锁主要发生在不同线程之间竞争锁的时候可以错开的情况,即不会发生阻塞等待的情况 在加锁的时候,每一个方法产生的栈帧会产生一个LockRecord,这个lockRecord会包含两部分内容,分别是当前的lockRecord的引用地址+00标志位、锁对象的引用地址。之后lockRecord部分会和锁对象的Markword进行CAS交换,当交换成功时则表示上锁成功,此时这个锁上的标志为是00,表示轻量级锁。
2.2 可重入锁
当执行一个方法的时候,需要再次获得锁,此时栈帧中会再次出现一个LockRecord,但是此时CAS操作一定会失败,因为锁对象的MarkWord的标志为已经是00了。但是由于ObjectReference指向的对象相同,因此判断为锁的可冲入操作。因此可以上锁成功。解锁的时候,若lockRecord的值为null则表示为一个可重入操作,直接表示成功并把相应位减一(表示去掉了一个重入锁)。
2.3 轻量级锁到重量级锁的转化(锁膨胀)
当一个线程持有了轻量级锁,其他线程在人家持有锁的同时尝试获得锁,此时这个锁就会升级为重量级锁。这就是锁膨胀的过程,此时,JVM会针对这个对象头申请一个moniter,其他尝试竞争的线程全部进入到entrylist中进行阻塞等待,moniter的owner对象就会指向已经持有这把锁的线程,锁对象的markword就会变成moniter的引用地址+10标志位。当线程结束的时候在尝试进行CAS,轻量级锁解锁时会发现锁已经膨胀。进而进入重量级锁的解锁流程。同时唤醒entrylist的线程。
3 重量级锁
3.1 重量级锁的加锁过程
锁对象会申请一个moniter对象,此时锁对象的markword会指向moniter的引用位置以及将标志置为10,且moniter的own属性会指向当前的线程;其他竞争的线程会进入到entrylist中进入阻塞等待。当解锁的时候,这个owner会置null,且锁对象的markword会回复为原来的010状态
3.2 竞争过程中的自旋优化
若线程1已经持有了锁对象完成了上锁过程,当线程2希望获得锁开始竞争的过程中,并不会直接进入阻塞状态进入moniter监视器的entrylist进行阻塞。而是首先采用CAS进行尝试获得锁,以避免上下文切换引起过高的代价