简单了解一下Mark Word(64bits)的结构:
如图,上面一个没有开启偏向锁,下面一个是偏向锁,它们的区别主要是在biased_lock的值不一样,0和1的区别。另外,一个记录的是hashcode,一个记录的是线程id。
这里有个小小知识点:
当偏向锁是立即启动的情况下,提前访问了对象的hashcode码(如上图红箭头),那么会禁用掉偏向锁。为什么呢?
原因:访问对象的时会生成hashcode码,存入了mark word ,而如果是偏向锁状态,那么将没有空间存储hashcode码(因为线程id就占了54位了),所以不得不禁用偏向锁,而变为非偏向锁。同时,为什么轻量级锁和重量级锁不会呢?因为轻量级锁是存在栈帧的锁记录里面,重量级锁是存在monitor里面的,将来解锁的时候还会恢复回去。
- 出现场景:轻量级锁在没有竞争时(没有竞争是指没有其它线程来相互竞争,但是当前线程会发生锁重入),每次重入仍然需要执行CAS操作。
- Java 6 中引入了偏向锁来实现优化,只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的,就表示没有竞争,不用重新CAS,以后只要不发生竞争,这个对象就归改线程所有。
- 如果开启了偏向锁(默认是开启,但是延迟的,如果需要立即生效,可以加VM参数-XX: BiasedLockingStartupDelay=0 来禁用延迟),那么对象创建之后,Mark Word值为0x0,即最后3位为101,这时它的thread、epoch、age都为0。
- 如果没有开启偏向锁,那么对象创建之后,mark word值为0x01,即最后3位为001,这时它的hashcode、age、都为0,第一次用到hsahcode时才会赋值。
- 当线程在mark word里面记录了线程id之后,就会一直记录,直到下一个线程使用这个对象时才会更改。另外,这个线程id是操作系统记录的线程id,不是Java程序里面直接用获取id方法得到的线程id。
- 如果程序设计的场景就是多个线程相互竞争这个对象的锁,那么此时就不适合使用偏向锁了,禁用偏向锁的方式是加VM参数:- XX: -UseBiasedLocking,冒号后面的减号就是代表禁用,如果是加号,就代表启用,当然默认是启用。
- 一般加锁的顺序,偏向锁->轻量级锁->重量级锁。
- 偏向锁的撤销:当其它线程使偏向锁对象时,会将偏向锁升级为轻量级锁(当线程1使用完偏向锁之后,线程2再使用该对象的偏向锁时发生)。
- 使用wait/notify会将偏向锁或者轻量级锁升级为重量级锁,原因是:只有重量级锁有wait/notify着这两个操作。
- 批量重偏向(优化):当线程1使用完对象的偏向锁时,线程2再次使用,这时会出现锁的转化,对象的锁从偏向锁转为轻量级锁,但是当线程2多次使用这个对象的锁时(阈值是20次),JVM会采用重偏向的策略,JVM觉得偏向错误,应该偏向线程2(因为线程2使用偏向锁更多),所以,线程2使用锁大于20次以上的