轻量级锁在没有竞争的时候(就自己这个线程),每次冲突依然需要执行 CAS
操作。
Java6
引入偏向锁进行优化:只有第一次使用 CAS
将线程 ID
设置到对象的 Mark Word
头,之后发现这个线程 ID
是自己的表示没有竞争,就不用重新 CAS
。以后只要不发生竞争,这个对象就归线程所有。
偏向锁的使用场景是,当多线程中,同步代码块只有一个线程操作的时候,才会考虑用这个。
根据 难搞的偏向锁终于被 Java 移除了 表明 JDK 15
之前,偏向锁默认是 enabled
,从 15
开始,默认就是 disabled
,除非显示的通过 UseBiasedLocking
开启。
偏向状态 来自于
1 2 3 4 5 6 7 8 9 10 11 12 13 |----------------------------------------------- |------------------- | | Mark Word(32 bits) | State | |------------------------------------------------------------------- | | hashcode: 25 |age: 4 | biased_lock:0 | 01 | Normal | |------------------------------------------------------------------- | | thread:23 |epoch:2 |age: 4 | biased_lock:1 | 01 | Biased | |------------------------------------------------------------------- | | ptr_to_lock_record: 30 | 00 | Lightweight Locked | |------------------------------------------------------------------- | | ptr_to_heavyweight_monitor:30 | 10 | Heavyweight Locked | |------------------------------------------------------------------- | | | 11 | Marker for GC | |------------------------------------------------------------------- |
一个对象创建时
如果开启了偏向锁「默认开启」,那么,对象创建后,mark word
值为 0x05 即最后 3
位是 101
,这时他的 thread
、epoch
、age
都是 0
虽然,默认开启,但是,启动后是延迟开启,所以,输出的值是 0x01
偏向锁是默认延迟的,不会在程序启动时立即生效,如果避免延迟,可以加 VM
参数 -XX:BiasedLockingStartupDelay=0
来禁用延迟
如果没有开启偏向锁,那么,对象创建后,markword
值为 0x01
即最后 3
位为 001
,这时它的 hashcode
、age
都是 0
,第一次用 hashcode
才会赋值
查看偏向锁 需要借助一个第 3 方的包。
1 2 3 4 5 <dependency > <groupId > org.openjdk.jol</groupId > <artifactId > jol-core</artifactId > <version > 0.9</version > </dependency >
就用上面的版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.redisc; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.openjdk.jol.info.ClassLayout; import java.util.concurrent.TimeUnit; @Data class User {} @Slf4j(topic = "c.Test" ) public class Test { public static void main(String[] args) throws Exception { User user1 = new User() ; log.debug(ClassLayout . parseInstance(user1 ) .to Printable() ); TimeUnit .SECONDS . sleep(5 ); log.debug(ClassLayout . parseInstance(user1 ) .to Printable() ); } }
根据 Java利用 ClassLayout 查看对象头 可知,里面的数据应该是倒叙「不过我也没对应起来」。
输出,以 java8 作为编译器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 11 :54 :18.311 [main] DEBUG c.Test - com.redisc.User object int ernals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000 ) (1 ) 对应输出 0x01 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000 ) (0 ) 8 4 (object header) 22 ef 00 f8 (00100010 11101111 00000000 11111000 ) (-134156510 ) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes int ernal + 4 bytes external = 4 bytes total 11 :54 :23.320 [main] DEBUG c.Test - com.redisc.User object int ernals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000 ) (1 ) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000 ) (0 ) 8 4 (object header) 22 ef 00 f8 (00100010 11101111 00000000 11111000 ) (-134156510 ) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes int ernal + 4 bytes external = 4 bytes total
在 VM
中加上 -XX:BiasedLockingStartupDelay=0
输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 11 :51 :25.708 [main] DEBUG c.Test - com.redisc.User object int ernals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000 ) (5 ) 对应输出 0x05 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000 ) (0 ) 8 4 (object header) 22 ef 00 f8 (00100010 11101111 00000000 11111000 ) (-134156510 ) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes int ernal + 4 bytes external = 4 bytes total 11 :51 :30.713 [main] DEBUG c.Test - com.redisc.User object int ernals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000 ) (5 ) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000 ) (0 ) 8 4 (object header) 22 ef 00 f8 (00100010 11101111 00000000 11111000 ) (-134156510 ) 12 4 (loss due to the next object alignment)
总结一下,很多课程说加上延迟后,就从 0x01
变成了 0x05
了,但是,我实际情况不是这样的。其他的倒是一样。
禁用和启用偏向锁 VM 参数
-XX:-UseBiasedLocking
可以禁用偏向锁
-XX:+UseBiasedLocking
可以启用偏向锁
Hashcode Monitor 的 Hashcode 只有用的时候才会产生。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.redisc;import lombok.Data;import lombok.extern.slf4j.Slf4j;import org.openjdk.jol.info.ClassLayout;import java.util.concurrent.TimeUnit;@Data class User {} @Slf 4j(topic = "c.Test" )public class Test { public static void main (String[] args) throws Exception { User user1 = new User(); user1.hashCode(); log.debug(ClassLayout.parseInstance(user1).toPrintable()); } }
输出
1 2 3 4 5 6 12 :25 :14.995 [main] DEBUG c.Test - com.redisc.User object int ernals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000 ) (1 ) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000 ) (0 ) 8 4 (object header) 22 ef 00 f8 (00100010 11101111 00000000 11111000 ) (-134156510 ) 12 4 (loss due to the next object alignment)
我们在看一下
1 2 3 4 5 6 7 8 9 10 11 12 13 |----------------------------------------------- |------------------- | | Mark Word(32 bits) | State | |------------------------------------------------------------------- | | hashcode: 25 |age: 4 | biased_lock:0 | 01 | Normal | |------------------------------------------------------------------- | | thread:23 |epoch:2 |age: 4 | biased_lock:1 | 01 | Biased | |------------------------------------------------------------------- | | ptr_to_lock_record: 30 | 00 | Lightweight Locked | |------------------------------------------------------------------- | | ptr_to_heavyweight_monitor:30 | 10 | Heavyweight Locked | |------------------------------------------------------------------- | | | 11 | Marker for GC | |------------------------------------------------------------------- |
可以看到 state = Biased
的时候,根本没有空存储 hashcode
,所以,当你使用 hashcode
的时候,即便你的状态是 Biased
也要转为 Normal
才能存储 Hashcode
进而使用 Hashcode
。
撤销偏向锁或者偏向状态 一共有两种情况
使用 HashCode
当有其他线程使用偏向锁的时候,会讲偏向锁升级为轻量级锁
批量重偏向 如果对象虽然被多个线程访问,但没有竞争,这是偏向了线程 T1
的对象仍有机会偏向 T2
,重偏向会重置偏向对象的 Thread ID
。
当撤销偏向锁超过 20
次之后,JVM
会认为撤销频率过大,于是给这些对象加锁时重新偏向至加锁线程。
如果撤销锁阈值超过 40
次,那么,整个类所有的对象都会变成不可偏向的,新建的对象也是不可偏向的。
这个建议看满一航老师的讲解,这里不再多叙述了。