概述
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。
Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现
这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有
static final Object obj = new Object();public static void m1() {synchronized( obj ) {// 同步块 Am2();}}public static void m2() {synchronized( obj ) {// 同步块 Bm3();}}public static void m3() {synchronized( obj ) {// 同步块 C}}
如上,轻量级锁每次调用synchronized,都会用锁记录来替换markword,而偏向锁会用当前的线程id来替换对象的markword,之后只要判断线程id是不是自己就行了
一个对象创建时:
- 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的 thread、epoch、age 都为 0
- 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -XX:BiasedLockingStartupDelay=0 来禁用延迟
如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值
测试延迟特性
public class Test_BiaseLock1 { public static void main(String[] args) throws InterruptedException { Object o1 = new Object(); synchronized (o1) { log.debug(SimpleClassLayout.parseInstance(o1).toPrintSimple()); } TimeUnit.SECONDS.sleep(5); Object o2 = new Object(); synchronized (o2) { log.debug(SimpleClassLayout.parseInstance(o2).toPrintSimple()); } } }结果
22:59:52.814 [main] DEBUG lock1 - 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000011 00010100 11110000 11110000 锁标识位为: 000 锁状态为: 轻量级锁、自旋锁 22:59:57.817 [main] DEBUG lock1 - 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000011 00110001 00111000 00000101 锁标识位为: 101 锁状态为: 偏向锁测试偏向锁
vmOption添加参数
-XX:BiasedLockingStartupDelay=0
@Slf4j(topic = "lock2")
public class Test_BiaseLock2 {
public static void main(String[] args) {
Object o = new Object();
log.debug("synchronized前:"+SimpleClassLayout.parseInstance(o).toPrintSimple());
synchronized (o) {
log.debug("synchronized中:"+SimpleClassLayout.parseInstance(o).toPrintSimple());
}
log.debug("synchronized后:"+SimpleClassLayout.parseInstance(o).toPrintSimple());
}
}
- 结果
23:04:25.704 [main] DEBUG lock2 - synchronized前: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 锁标识位为: 101 锁状态为: 偏向锁 23:04:25.706 [main] DEBUG lock2 - synchronized中: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000010 11110000 00111000 00000101 锁标识位为: 101 锁状态为: 偏向锁 23:04:25.707 [main] DEBUG lock2 - synchronized后: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000010 11110000 00111000 00000101 锁标识位为: 101 锁状态为: 偏向锁注意 处于偏向锁的对象解锁后,线程 id 仍存储于对象头中
测试禁用
上一节代码添加vmOption参数
-XX:-UseBiasedLocking结果
23:06:05.331 [main] DEBUG lock2 - synchronized前: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 锁标识位为: 001 锁状态为: 无锁 23:06:05.341 [main] DEBUG lock2 - synchronized中: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000010 01101101 11110000 10111000 锁标识位为: 000 锁状态为: 轻量级锁、自旋锁 23:06:05.341 [main] DEBUG lock2 - synchronized后: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 锁标识位为: 001 锁状态为: 无锁测试 hashCode
正常状态对象一开始是没有 hashCode 的,第一次调用才生成
vmOption添加参数
-XX:BiasedLockingStartupDelay=0@Slf4j(topic = "lock3") public class Test_BiaseLock3 { public static void main(String[] args) { Object o = new Object(); o.hashCode(); log.debug("synchronized前:"+SimpleClassLayout.parseInstance(o).toPrintSimple()); synchronized (o) { log.debug("synchronized中:"+SimpleClassLayout.parseInstance(o).toPrintSimple()); } log.debug("synchronized后:"+SimpleClassLayout.parseInstance(o).toPrintSimple()); } }结果
23:07:54.402 [main] DEBUG lock3 - synchronized前: 64位Mark Word信息: 00000000 00000000 00000000 01100001 10010011 10111000 01000101 00000001 锁标识位为: 001 锁状态为: 无锁 23:07:54.405 [main] DEBUG lock3 - synchronized中: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000010 11100100 11110011 01011000 锁标识位为: 000 锁状态为: 轻量级锁、自旋锁 23:07:54.405 [main] DEBUG lock3 - synchronized后: 64位Mark Word信息: 00000000 00000000 00000000 01100001 10010011 10111000 01000101 00000001 锁标识位为: 001 锁状态为: 无锁-
原理

在没有调用hashcode时,hashcode都是0
调用了hashcode,hashcode就不为0了,即便开启了偏向锁,但是偏向锁需要占用存储hashcode的位来存储线程id,和Epoch,这样hashcode就消失了,因此有hashcode的对象无法被偏向 轻量级锁会在锁记录中记录 hashCode
- 重量级锁会在 Monitor 中记录 hashCode
测试偏向锁升级轻量级锁
当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁
package me.ezerror.mutilthreading.D0.Ch03;
import lombok.extern.slf4j.Slf4j;
import me.ezerror.util.simpleclasslayout.SimpleClassLayout;
import java.util.concurrent.TimeUnit;
@Slf4j(topic = "lock4")
public class Test_BiaseLock4 {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
log.debug("synchronized前:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
synchronized (o) {
log.debug("synchronized中:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
}
log.debug("synchronized后:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
log.debug("synchronized前:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
synchronized (o) {
log.debug("synchronized中:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
}
log.debug("synchronized后:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
},"t2").start();
}
}
- 结果 ```java 23:24:44.337 [main] DEBUG lock4 - synchronized前: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 锁标识位为: 101 锁状态为: 偏向锁 23:24:44.337 [main] DEBUG lock4 - synchronized中: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000010 10010010 00111000 00000101 锁标识位为: 101 锁状态为: 偏向锁 23:24:44.337 [main] DEBUG lock4 - synchronized后: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000010 10010010 00111000 00000101 锁标识位为: 101 锁状态为: 偏向锁
23:24:45.350 [t2] DEBUG lock4 - synchronized前: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000010 10010010 00111000 00000101 锁标识位为: 101 锁状态为: 偏向锁 23:24:45.350 [t2] DEBUG lock4 - synchronized中: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00101001 10011000 11101101 01010000 锁标识位为: 000 锁状态为: 轻量级锁、自旋锁 23:24:45.350 [t2] DEBUG lock4 - synchronized后: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 锁标识位为: 001 锁状态为: 无锁
<a name="jvEqR"></a>
# 测试偏向锁撤销 - 调用 wait/notify
```java
@Slf4j(topic = "lock5")
public class Test_BiaseLock5 {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
new Thread(() -> {
log.debug(SimpleClassLayout.parseInstance(o).toPrintSimple());
synchronized (o) {
log.debug("wait前:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
try {
o.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("wait后:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
}
}, "t1").start();
// 等待t1执行到wait
TimeUnit.SECONDS.sleep(3);
new Thread(() -> {
synchronized (o) {
log.debug("notify");
o.notify();
}
}, "t2").start();
// 等待t1执行完毕
TimeUnit.SECONDS.sleep(1);
log.debug("最终:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
}
}
结果
23:36:35.036 [t1] DEBUG lock5 - 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 锁标识位为: 101 锁状态为: 偏向锁 23:36:35.036 [t1] DEBUG lock5 - wait前: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00100111 11101101 10001000 00000101 锁标识位为: 101 锁状态为: 偏向锁 23:36:36.036 [t2] DEBUG lock5 - notify 23:36:36.036 [t1] DEBUG lock5 - wait后: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00100110 01000110 00100011 11001010 锁标识位为: 010 锁状态为: 重量级锁 23:36:37.040 [main] DEBUG lock5 - 最终: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 锁标识位为: 001 锁状态为: 无锁批量重偏向
如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID
- 当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程
添加参数
-XX:BiasedLockingStartupDelay=0 -XX:BiasedLockingBulkRebiasThreshold=5 //偏向锁批量重偏向阈值@Slf4j(topic = "lock6") public class Test_BiaseLock6 { static Thread t1; static Thread t2; public static void main(String[] args) throws InterruptedException { Vector<Lock> list = new Vector<>(); int loopNumber = 5; t1 = new Thread(() -> { for (int i = 0; i < loopNumber; i++) { Lock o = new Lock(); list.add(o); synchronized (o) { log.debug(++i + "\t" + SimpleClassLayout.parseInstance(o).toPrintSimple(false)); } i--; } LockSupport.unpark(t2); }, "t1"); t1.start(); t2 = new Thread(() -> { LockSupport.park(); log.debug("================================"); for (int i = 0; i < loopNumber; i++) { Lock o = list.get(i++); synchronized (o) { } log.debug(i-- + "\t" + SimpleClassLayout.parseInstance(o).toPrintSimple(false)); } }, "t2"); t2.start(); t2.join(); log.debug(SimpleClassLayout.parseInstance(new Lock()).toPrintSimple(false)); } static class Lock { } }结果
[t1] DEBUG lock6 - 1 00000000 00000000 00000000 00000000 00101001 11100110 11101000 00000101 [t1] DEBUG lock6 - 2 00000000 00000000 00000000 00000000 00101001 11100110 11101000 00000101 [t1] DEBUG lock6 - 3 00000000 00000000 00000000 00000000 00101001 11100110 11101000 00000101 [t1] DEBUG lock6 - 4 00000000 00000000 00000000 00000000 00101001 11100110 11101000 00000101 [t1] DEBUG lock6 - 5 00000000 00000000 00000000 00000000 00101001 11100110 11101000 00000101 [t2] DEBUG lock6 - ================================ [t2] DEBUG lock6 - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] DEBUG lock6 - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] DEBUG lock6 - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] DEBUG lock6 - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] DEBUG lock6 - 5 00000000 00000000 00000000 00000000 00101001 11100110 11111001 00000101-
批量撤销
当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象
都会变为不可偏向的,新建的对象也是不可偏向的 添加参数
-XX:BiasedLockingStartupDelay=0 -XX:BiasedLockingBulkRevokeThreshold=5 //偏向锁批量撤销阈值```java package me.ezerror.mutilthreading.D0.Ch03;
import lombok.extern.slf4j.Slf4j; import me.ezerror.util.simpleclasslayout.SimpleClassLayout; import org.openjdk.jol.info.ClassLayout;
import java.util.Vector; import java.util.concurrent.locks.LockSupport;
@Slf4j(topic = “lock6”) public class Test_BiaseLock7 { static Thread t1; static Thread t2; static Thread t3;
public static void main(String[] args) throws InterruptedException {
Vector<Lock> list = new Vector<>();
int loopNumber = 4;
t1 = new Thread(() -> {
for (int i = 0; i < loopNumber; i++) {
int h = i + 1;
Lock o = new Lock();
list.add(o);
synchronized (o) {
log.debug(h + "\t" + SimpleClassLayout.parseInstance(o).toPrintSimple(false));
}
}
LockSupport.unpark(t2);
}, "t1");
t1.start();
t2 = new Thread(() -> {
LockSupport.park();
log.debug("================================");
for (int i = 0; i < loopNumber; i++) {
int h = i + 1;
Lock o = list.get(i);
// log.debug(i + "\t" + SimpleClassLayout.parseInstance(o).toPrintSimple(false));
synchronized (o) {
// log.debug(i + "\t" + SimpleClassLayout.parseInstance(o).toPrintSimple(false));
}
log.debug(h + "\t" + SimpleClassLayout.parseInstance(o).toPrintSimple(false));
}
LockSupport.unpark(t3);
}, "t2");
t2.start();
t2.join();
log.debug(SimpleClassLayout.parseInstance(new Lock()).toPrintSimple(false));
}
public static class Lock {
}
}
- 结果
```java
[t1] DEBUG lock6 - 1 00000000 00000000 00000000 00000000 00101010 00010001 01110000 00000101
[t1] DEBUG lock6 - 2 00000000 00000000 00000000 00000000 00101010 00010001 01110000 00000101
[t1] DEBUG lock6 - 3 00000000 00000000 00000000 00000000 00101010 00010001 01110000 00000101
[t1] DEBUG lock6 - 4 00000000 00000000 00000000 00000000 00101010 00010001 01110000 00000101
[t1] DEBUG lock6 - 5 00000000 00000000 00000000 00000000 00101010 00010001 01110000 00000101
[t2] DEBUG lock6 - ================================
[t2] DEBUG lock6 - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] DEBUG lock6 - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] DEBUG lock6 - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] DEBUG lock6 - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] DEBUG lock6 - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[main] DEBUG lock6 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
- 发现撤销了5个之后,再生成对象已经不使用偏向锁了
循环改为四次,再执行
[t1] DEBUG lock6 - 1 00000000 00000000 00000000 00000000 00101001 00000001 10011000 00000101 [t1] DEBUG lock6 - 2 00000000 00000000 00000000 00000000 00101001 00000001 10011000 00000101 [t1] DEBUG lock6 - 3 00000000 00000000 00000000 00000000 00101001 00000001 10011000 00000101 [t1] DEBUG lock6 - 4 00000000 00000000 00000000 00000000 00101001 00000001 10011000 00000101 [t2] DEBUG lock6 - ================================ [t2] DEBUG lock6 - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] DEBUG lock6 - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] DEBUG lock6 - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [t2] DEBUG lock6 - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 [main] DEBUG lock6 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101发现此时还是可以使用偏向锁的
