synchronized批量重偏向与批量撤销
- 批量重偏向:如果一个类的大量对象被一个线程T1执行了同步操作,也就是大量对象先偏向了T1,T1同步结束后,另一个线程也将这些对象作为锁对象进行操作,会导偏向锁重偏向的操作。
- 批量撤销:当一个偏向锁如果撤销次数到达40的时候就认为这个对象设计的有问题;那么JVM会把这个对象所对应的类所有的对象都撤销偏向锁;并且新实例化的对象也是不可偏向的。
可以通过命令java -XX:+PrintFlagsFinal -version|grep 'BiasedLocking'
来看JVM中的默认配置:
intx BiasedLockingBulkRebiasThreshold = 20 {
product}
intx BiasedLockingBulkRevokeThreshold = 40 {
product}
intx BiasedLockingDecayTime = 25000 {
product}
intx BiasedLockingStartupDelay = 4000 {
product}
bool TraceBiasedLocking = false {
product}
bool UseBiasedLocking = true {
product}
- BiasedLockingBulkRebiasThreshold:偏向锁批量重偏向的默认阀值为20次。
- BiasedLockingBulkRevokeThreshold:偏向锁批量撤销的默认阀值为40次。
- BiasedLockingDecayTime:距上次批量重偏向25秒内,撤销计数达到40,就会发生批量撤销。每隔(>=)25秒,会重置在[20, 40)内的计数,这意味着可以发生多次批量重偏向。
批量重偏向(BulkRebias)
测试代码:
package com.morris.concurrent.syn.batch;
import org.openjdk.jol.info.ClassLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class BulkBias {
private static Thread t1, t2;
public static void main(String[] args) throws InterruptedException {
// 延时产生可偏向对象
TimeUnit.SECONDS.sleep(5);
List<B> objects = new ArrayList<>(); // 创建50个对象,锁状态为101,匿名偏向锁
for (int i = 0; i < 50; i++) {
objects.add(new B());
}
t1 = new Thread(() -> {
for (int i = 0; i < objects.size(); i++) {
synchronized (objects.get(i)) {
// 50个对象全部偏向t1 101
}
}
LockSupport.unpark(t2);
});
t2 = new Thread(() -> {
LockSupport.park();
//这里面只循环了30次!!!
for (int i = 0; i < 30; i++) {
Object a = objects.get(i);
synchronized (a) {
//分别打印第19次和第20次偏向锁重偏向结果
if (i == 18 || i == 19) {
System.out.println("第" + (i + 1) + "次偏向结果");
System.out.println((ClassLayout.parseInstance(a).toPrintable())); // 第19次轻量级锁00,第20次偏向锁101,偏向t2
}
}
}
});
t1.start();
t2.start();
t2.join();
System.out.println("打印list中第11个对象的对象头:");
System.out.println((ClassLayout.parseInstance(objects.get(10)).toPrintable())); // 01 无锁
System.out.println("打印list中第26个对象的对象头:");
System.out.println((ClassLayout.parseInstance(objects.get(25)).toPrintable())); // 101 偏向t2
System.out.println("打印list中第41个对象的对象头:");
System.out.println((ClassLayout.parseInstance(objects.get(40)).toPrintable())); // 101 偏向t1
}
}
class B {
}
运行结果如下:
第19次偏向结果
com.morris.concurrent.syn.batch.B object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 40 f3 bc 1d (01000000 11110011 10111100 00011101) (498922304)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
第20次偏向结果
com.morris.concurrent.syn.batch.B object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 69 6c 1e (00000101 01101001 01101100 00011110) (510421253)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
打印list中第11个对象的对象头:
com.morris.concurrent.syn.batch.B object internals:
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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
打印list中第26个对象的对象头:
com.morris.concurrent.syn.batch.B object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 69 6c 1e (00000101 01101001 01101100 00011110) (510421253)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
打印list中第41个对象的对象头:
com.morris.concurrent.syn.batch.B object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 60 6c 1e (00000101 01100000 01101100 00011110) (510418949)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
运行结果分析:
- 当一个线程t1运行结束后,所有的对象都偏向t1。
- 线程t2只对前30个对象进行了同步,0-18的对象会偏向锁(101)升级为轻量级锁(00),19-29的对象由于撤销次数达到20,触发批量重偏向,偏向线程t2。
- t2结束后,0-18的对象由轻量级锁释放后变成了无锁,19-29的对象偏向t2,30-49的对象还是偏向t1。
总结:批量重偏向会以class为单位,为每个class维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操作时,该计数器+1,当这个值达到重偏向阈值(默认20)时,JVM就认为该class的偏向锁有问题,因此会进行批量重偏向。
批量撤销(BulkRevoke)
测试代码如下:
package com.morris.concurrent.syn.batch;
import org.openjdk.jol.info.ClassLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class BulkBiasAndRevoke {
private static Thread t1, t2, t3, t4;
public static void main(String[] args) throws InterruptedException {
TimeUnit.SECONDS.sleep(5); // 等待偏向延迟时间到达
List<L> list = new ArrayList<>();
for (int i = 0; i < 80; i++) {
list.add(new L());
}
t1 = new Thread(() -> {
for (int i = 0; i < 60; i++) {
L l = list.get(i);
synchronized (l) {
}
}
LockSupport.unpark(t2);
}, "t1");
t2 = new Thread(() -> {
LockSupport.park();
for (int i = 0; i < 60; i++) {
L l = list.get(i);
synchronized (l) {
}
}
}, "t2");
t3 = new Thread(() -> {
LockSupport.park();
System.out.println("t3");
for (int i = 0; i < 60; i++) {
L l = list.get(i);
// 0-18 01
// 19-59 101 偏向t2
synchronized (l) {
// 0-59 00
}
// 0-59 01
}
}, "t3");
t4 = new Thread(() -> {
synchronized (list.get(65)) {
System.out.println("t4 begin" + ClassLayout.parseInstance(list.get(65)).toPrintable()); // 101
LockSupport.unpark(t3);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t4 end" + ClassLayout.parseInstance(list.get(65)).toPrintable()); // 00
System.out.println("t4 end" + ClassLayout.parseInstance(list.get(66)).toPrintable()); // 101
}
}, "t1");
t4.start();
t1.start();
t2.start();
t3.start();
t3.join();
t4.join();
System.out.println(ClassLayout.parseInstance(new L()).toPrintable()); // 01
}
}
class L {
}
运行结果如下:
t4 begincom.morris.concurrent.syn.batch.L object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 61 79 1e (00000101 01100001 01111001 00011110) (511271173)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
t4 endcom.morris.concurrent.syn.batch.L object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 28 ef c2 1d (00101000 11101111 11000010 00011101) (499314472)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
t4 endcom.morris.concurrent.syn.batch.L object internals:
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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
com.morris.concurrent.syn.batch.L object internals:
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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
- t1执行完后,0-59的对象偏向t1。
- t2执行完后,0-18的对象为无锁,19-59偏向t2。
- t3执行时由于之前执行过批量重偏向了,所以这里会升级为轻量级锁。
- t4休眠前对象65为匿名偏向状态,t4休眠后,由于触发了批量撤销,所以锁状态变为轻量级锁,所以批量撤销会把正在执行同步的对象的锁状态由偏向锁变为轻量级锁,而不在执行同步的对象的锁状态不会改变(如对象66)。
总结:批量重偏向和批量撤销是针对类的优化,和对象无关。偏向锁重偏向一次之后不可再次重偏向。当某个类已经触发批量撤销机制后,JVM会默认当前类产生了严重的问题,剥夺了该类的新实例对象使用偏向锁的权利。