目录
偏向锁
用途
偏向状态
偏向延迟
加锁
锁撤销
调用对象 hashcode
其他线程使用该对象锁
wait / notify
批量重偏向
批量撤销
锁粗化 && 锁消除
偏向锁
用途
public void method1(){ synchronized(object){ method2(); } } public void method2(){ synchronized(object){ method3(); } } public void method3(){ synchronized(object){ // 执行代码 } }
没有线程竞争时,持锁线程反复获取锁(锁重入);每次都会生成锁记录以及进行 CAS 操作
Java6 引入偏向锁进行优化;第一次 CAS 操作,将线程 ID 设置到对象头 Mark Word 中;之后,查看线程 ID 为自己,则表示无竞争,不进行 CAS 操作;以后,只要没有竞争,锁归该线程所有
偏向状态
一个对象创建时:
① 如果开启了偏向锁(默认开启),Mark Word 的值为 0x05 即二进制后三位 101;thread、epoch、age 均为 0
② 如果未开启,Mark Word 的值为 0x01 即二进制后三位 001;hashcode、age 均为 0
③ 偏向锁默认延迟开启,不会立即生效
BiasedLockingStartupDelay=4000 // 默认延迟 4s -XX:BiasedLockingStartupDelay=0 // 加 VM 参数来禁用延迟
偏向延迟
① 默认延迟 (001)正常无锁状态
@Slf4j(topic = "c.MarkWord") public class MarkWord { public static void main(String[] args) throws InterruptedException { MyInteger myInteger = new MyInteger(0); log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); } }
② 睡 5s 后(101)变为偏向锁
@Slf4j(topic = "c.MarkWord") public class MarkWord { public static void main(String[] args) throws InterruptedException { Thread.sleep(5000); MyInteger myInteger = new MyInteger(0); log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); } }
加锁
① 直接创建然后加锁(00)轻量级锁
@Slf4j(topic = "c.MarkWord") public class MarkWord { public static void main(String[] args) throws InterruptedException { MyInteger myInteger = new MyInteger(0); synchronized (myInteger){ log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); } } }
101)变为偏向锁
@Slf4j(topic = "c.MarkWord") public class MarkWord { public static void main(String[] args) throws InterruptedException { Thread.sleep(5000); MyInteger myInteger = new MyInteger(0); synchronized (myInteger){ log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); } } }
锁撤销
调用对象 hashcode
原因:偏向锁状态下,Mark Word 要用 54 位来存线程 ID,没有位置存 hashcode
无锁状态下:存于 Mark Word 中(初始为 0)
轻量级锁:hashcode 存于锁记录中
重量级锁:hashcode 存于 monitor 中
@Slf4j(topic = "c.MarkWord") public class MarkWord { public static void main(String[] args) throws InterruptedException { MyInteger myInteger = new MyInteger(0); log.debug("--------------------------------调用 hashcode 前--------------------------------"); log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); myInteger.hashCode(); log.debug("--------------------------------调用 hashcode 后--------------------------------"); log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); } }
其他线程使用该对象锁
原因:多个线程获取同一把锁,但是没有竞争,交错获取;初始为偏向锁,其他线程获取时,会将其升级为轻量级锁(一般情况)
@Slf4j(topic = "c.MarkWord") public class MarkWord { public static void main(String[] args) throws InterruptedException { MyInteger myInteger = new MyInteger(0); new Thread(() -> { synchronized (myInteger){ log.debug("--------------------------------线程 t 使用该对象锁--------------------------------"); log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); } }, "t").start(); Thread.sleep(5000); log.debug("--------------------------------线程 t 使用结束--------------------------------"); log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); synchronized (myInteger){ log.debug("--------------------------------main 使用该对象锁--------------------------------"); log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); } } }
wait / notify
原因:wait / notify 只有重量级锁才能使用;调用之后,升级为重量级锁
@Slf4j(topic = "c.MarkWord")
public class MarkWord {
public static void main(String[] args) throws InterruptedException {
MyInteger myInteger = new MyInteger(0);
new Thread(() -> {
synchronized (myInteger){
log.debug("--------------------------------线程 t 使用该对象锁--------------------------------");
log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
log.debug("--------------------------------线程 t 调用 wait--------------------------------");
try {
myInteger.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("--------------------------------线程 t wait 结束--------------------------------");
log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
}
}, "t").start();
Thread.sleep(5000);
synchronized (myInteger){
log.debug("--------------------------------main 使用该对象锁--------------------------------");
log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
log.debug("--------------------------------main 调用 notify--------------------------------");
myInteger.notify();
}
}
}
批量重偏向(难点)
BiasedLockingBulkRebiasThreshold=20 // 开启批量重偏向的阈值
① 同一个类,它对象的偏向锁被撤销达到 20 次,则会进行批量重偏向(从第二十次开始)
② 线程第一次遇到偏向锁都会先撤销,批量重偏向不算做锁撤销
③ 如一开始,偏向锁偏向 t1,它有机会偏向其他线程;重偏向之后,保存的线程 ID 会重置
@Slf4j(topic = "c.MarkWord")
public class MarkWord {
public static void main(String[] args) throws InterruptedException {
List<MyInteger> list = new ArrayList();
new Thread(() -> {
for(int i = 0; i < 30; i++){
MyInteger myInteger = new MyInteger();
list.add(myInteger);
log.debug(i + "---------------------------加锁前---------------------------");
log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
synchronized (myInteger){
log.debug(i + "---------------------------加锁中---------------------------");
log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
}
log.debug(i + "---------------------------加锁后---------------------------");
log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
}
synchronized (MyInteger.class){
MyInteger.class.notify();
}
}, "t1").start();
new Thread(() -> {
synchronized (MyInteger.class){
try {
MyInteger.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(int i = 0; i < list.size(); i++){
log.debug(i + "---------------------------加锁前---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
synchronized (list.get(i)){
log.debug(i + "---------------------------加锁中---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
}
log.debug(i + "---------------------------加锁后---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
}
}, "t2").start();
}
}
1. 初始化,线程 t1 中给这 30 个对象加的都是偏向锁
2. 然后,线程 t2 获取这 30 把锁时,0 ~ 18 都升级为轻量级锁,19 ~ 29 都重新偏向 t2
3. 这样操作,在 t3 里撤销第 20 次,则 19 ~ 29 都会重新偏向 t3
@Slf4j(topic = "c.MarkWord")
public class MarkWord {
static Thread t1, t2, t3;
public static void main(String[] args) throws InterruptedException {
List<MyInteger> list = new ArrayList();
t1 = new Thread(() -> {
for(int i = 0; i < 30; i++){
MyInteger myInteger = new MyInteger();
list.add(myInteger);
log.debug(i + "---------------------------加锁前---------------------------");
log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
synchronized (myInteger){
log.debug(i + "---------------------------加锁中---------------------------");
log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
}
log.debug(i + "---------------------------加锁后---------------------------");
log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
}
LockSupport.unpark(t2);
}, "t1");
t1.start();
t2 = new Thread(() -> {
LockSupport.park();
for(int i = 0; i < 19; i++){
log.debug(i + "---------------------------加锁前---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
synchronized (list.get(i)){
log.debug(i + "---------------------------加锁中---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
}
log.debug(i + "---------------------------加锁后---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
}
LockSupport.unpark(t3);
}, "t2");
t2.start();
t3 = new Thread(() -> {
LockSupport.park();
for(int i = 19; i < list.size(); i++){
log.debug(i + "---------------------------加锁前---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
synchronized (list.get(i)){
log.debug(i + "---------------------------加锁中---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
}
log.debug(i + "---------------------------加锁后---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
}
}, "t3");
t3.start();
}
}
批量撤销(难点)
BiasedLockingBulkRevokeThreshold=40 // 开启批量撤销的阈值 BiasedLockingDecayTime=25000 // 达到阈值就马上进行批量撤销的时间范围
① 锁撤销次数达到 40 次并且在 25s 内,则开始批量撤销
② 批量撤销:整个类的对象都不可偏向,包括新建的对象
③ 时间 >= 25s 时,重置在 [20, 40) 内的次数,可起到再次批量重偏向的作用
1. 重偏向时,不算做撤销;如:即使到第 100 把锁,都还是重偏向
2.1. 创建 39 把锁(开始重偏向只撤销了 19 把锁,第 20 把还是偏向锁,还剩 20 把偏向锁);
2.2. 由之前的案例可得:0 ~ 18 在 t2 中升级为轻量级锁,19 ~ 38 仍然为偏向锁;
2.3. t3 中,0 ~ 18 不会影响撤销数(已经为轻量级锁),19 ~ 38 依次被撤销,撤销数达到 40 开始批量撤销;新对象初始态为无锁状态
@Slf4j(topic = "c.MarkWord")
public class MarkWord {
static Thread t1, t2, t3;
public static void main(String[] args) throws InterruptedException {
List<MyInteger> list = new ArrayList();
t1 = new Thread(() -> {
for(int i = 0; i < 39; i++){
MyInteger myInteger = new MyInteger();
list.add(myInteger);
log.debug(i + "---------------------------加锁前---------------------------");
log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
synchronized (myInteger){
log.debug(i + "---------------------------加锁中---------------------------");
log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
}
log.debug(i + "---------------------------加锁后---------------------------");
log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
}
LockSupport.unpark(t2);
}, "t1");
t1.start();
t2 = new Thread(() -> {
LockSupport.park();
for(int i = 0; i < list.size(); i++){
log.debug(i + "---------------------------加锁前---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
synchronized (list.get(i)){
log.debug(i + "---------------------------加锁中---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
}
log.debug(i + "---------------------------加锁后---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
}
LockSupport.unpark(t3);
}, "t2");
t2.start();
t3 = new Thread(() -> {
LockSupport.park();
for(int i = 0; i < list.size(); i++){
log.debug(i + "---------------------------加锁前---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
synchronized (list.get(i)){
log.debug(i + "---------------------------加锁中---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
}
log.debug(i + "---------------------------加锁后---------------------------");
log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
}
log.debug(ClassLayout.parseInstance(new MyInteger()).toPrintable()); // 新建对象,查看状态
}, "t3");
t3.start();
}
}
3. 撤销数在 [20, 40) 内,超过 25s,撤销数已重置;次数不在该范围,不会重置if(i == 38){ // 使其 25s 内完成不了 try { Thread.sleep(26000); } catch (InterruptedException e) { e.printStackTrace(); } }
锁粗化 && 锁消除
锁粗化:加锁、解锁会耗损性能;对锁不要过度细化,有时需要将锁范围扩大
锁消除:指的是虚拟机既时编辑器在运行时候,对一些代码上要求同步,但是对被检测到不可能存在共享数据竞争的锁进行一个消除(依据逃逸分析)。