偏向锁
概念
根据计算机的 二八理论 而言,临界区被访问时,有 80% 的可能是同一个线程,所以偏向锁在这种情况下可以极大的提升 synchronized 关键字的性能
那偏向锁既然这么有用,为何还要提供关闭的功能呢?在竞争频繁的场景下,偏向锁仅仅只会导致 synchronized 关键字的性能降低,因为撤销偏向锁通常会导致 STW 的发生,除非对象都在当前虚拟机栈
文档
可见 synchronized 官方文档最后一个参考文档
应用
JVM 设置参数
# 开启偏向锁
-XX:+UseBiasedLocking
# 关闭偏向锁延迟
-XX:BiasedLockingStartupDelay=0
# 查看所有的 JVM 参数
-XX:+PrintFlagsFinal
# 设置重偏向阈值
-XX:BiasedLockingBulkRebiasThreshold=20
# 批量重偏向距离上次批量重偏向的后重置的延迟时间
-XX:BiasedLockingDecayTime=25000
# 设置批量撤销阈值
-XX:BiasedLockingBulkRevokeThreshold=40
在 JVM 启动的时候会有很多线程在后台运行,例如 GC 线程,Finalizer 线程,VM Thread 线程等,会用到很多同步操作,所以在启动的前 4 秒默认创建的对象都不支持偏向,因为有默认参数
-XX:BiasedLockingStartupDelay=4000
请注意加入依赖
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.8</version>
</dependency>
匿名偏向
public class BiasedLockDemo1 {
public static void main(String[] args) throws InterruptedException {
// JVM 虚拟机启动的时候创建
Model model1 = new Model();
// 无锁
System.out.println(ClassLayout.parseInstance(model1).toPrintable());
Thread.sleep(4001);
// JVM 启动 4 秒后创建对象
Model model2 = new Model();
// 偏向锁
System.out.println(ClassLayout.parseInstance(model2).toPrintable());
}
}
由上可见,在对象,在支持偏向之前创建的对象都是无锁
状态的,但是在支持偏向后,创建的对象就自动带有偏向标识,但是此时是没有偏向任何线程的,属于一个匿名偏向(anonymously biased)
状态,此时对象可以偏向任何一个线程,详情可以参加 bytecodeInterpreter.cpp
偏向
在第一个案例中,我们加一把锁,可以看到不一样的信息
public class BiasedLockDemo2 {
public static void main(String[] args) throws InterruptedException {
// JVM 虚拟机启动的时候创建
Model model1 = new Model();
// 无锁
System.out.println(ClassLayout.parseInstance(model1).toPrintable());
Thread.sleep(4001);
// JVM 启动 4 秒后创建对象
Model model2 = new Model();
// 偏向锁
System.out.println(ClassLayout.parseInstance(model2).toPrintable());
synchronized (model2) {
// 偏向锁
System.out.println(ClassLayout.parseInstance(model2).toPrintable());
}
}
}
可以看到此时对象头有了线程 ID 的信息
偏向锁升级
偏向锁在退出后,如果有另一个线程来竞争锁资源,会升级为轻量级锁
public class BiasedLockDemo2 {
public static void main(String[] args) throws InterruptedException {
// JVM 虚拟机启动的时候创建
Model model1 = new Model();
// 无锁
System.out.println(ClassLayout.parseInstance(model1).toPrintable());
Thread.sleep(4001);
// JVM 启动 4 秒后创建对象
Model model2 = new Model();
// 偏向锁
System.out.println(ClassLayout.parseInstance(model2).toPrintable());
synchronized (model2) {
// 偏向锁
System.out.println(ClassLayout.parseInstance(model2).toPrintable());
}
synchronized (model2) {
// 偏向锁
System.out.println(ClassLayout.parseInstance(model2).toPrintable());
}
new Thread(() -> {
synchronized (model2) {
// 轻量级锁
System.out.println(ClassLayout.parseInstance(model2).toPrintable());
}
}).start();
}
}
此时可以看见,相同的 main 线程进入的时候,锁没有升级,此时还是偏向锁,但是当其他的线程进入的时候,偏向锁便升级为了轻量级锁,轻量级锁在下面会说,不在这里讨论
如果在偏向锁还没有退出同步代码块的时候,另一个线程来竞争这个锁资源,会直接升级为重量级锁
public class HeavyweightLockDemo {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
Model model = new Model();
System.out.println(ClassLayout.parseClass(Model.class).toPrintable());
Thread t0 = new Thread(() -> {
synchronized (model) {
System.out.println("t0 begin 1");
System.out.println(ClassLayout.parseInstance(model).toPrintable() + "-------------------1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t0 begin 2");
System.out.println(ClassLayout.parseInstance(model).toPrintable() + "-------------------2");
}
});
Thread t1 = new Thread(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 begin");
synchronized (model) {
System.out.println(ClassLayout.parseInstance(model).toPrintable());
}
});
t0.start();
t1.start();
}
}
批量重偏向
当在一批对象偏向一个线程后,如果另一个线程批量去给这批对象进行撤销偏向锁,并升级为轻量级锁,那么虚拟机会在达到一定的阈值时,会将 class 中的 epoch 值改为 01,但是已经创建的对象 epoch 值都为 00,对比两者不同,于是发生重偏向,将同步块内部的对象中的对象头中偏向的线程 ID 指向自己
public class BiasedLockDemo3 {
// 同步容器
public static List<Model> list = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
// 检查点
List<Integer> checkPoint = Arrays.asList(9,19,49);
Thread.sleep(5000);
// JVM 启动 4 秒后创建对象
Thread t0 = new Thread(() -> {
for (int i = 0; i < 50; i++) {
Model model = new Model();
synchronized (model) {
// 等待这个是为了展示 epoch 的值
if (i == 49) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(model);
if (checkPoint.contains(i)) {
System.out.println();
// 偏向锁
System.out.println(ClassLayout.parseInstance(model).toPrintable() + "add----------------------" + i);
}
}
}
});
Thread t1 = new Thread(() -> {
for (int i = 0; i < list.size(); i++) {
Model model = list.get(i);
synchronized (model) {
if (checkPoint.contains(i)) {
System.out.println();
// 在 20 个之前都是轻量级锁,在 20 个以及之后是偏向锁
System.out.println(ClassLayout.parseInstance(model).toPrintable() + "t1----------------------" + i);
}
}
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Model model49 = list.get(49);
synchronized (model49) {
System.out.println();
// 故意等着第 49 个对象进来查看 mark word 信息
// 轻量级锁
System.out.println(ClassLayout.parseInstance(model49).toPrintable() + "t1----------------------" + 49);
}
}, "t1");
t0.start();
Thread.sleep(4000);
t1.start();
t1.join();
// 偏向锁,可以看到 epoch 值是在跟着 class 走的
System.out.println(ClassLayout.parseInstance(new Model()).toPrintable() + "main----------------------");
}
}
如果我们在重偏向一次后,又进行了第二次重偏向,不同的情况会出现不同的结果,如果第一次重偏向后经历了 25 秒之后,就会重置重偏向的功能,此时,对象又可以被重偏向了
public class BiasedLockDemo4 {
// 同步容器
public static List<Model> list = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
// 检查点
List<Integer> checkPoint = Arrays.asList(9,19,49);
Thread.sleep(5000);
// JVM 启动 4 秒后创建对象
Thread t0 = new Thread(() -> {
for (int i = 0; i < 50; i++) {
Model model = new Model();
synchronized (model) {
list.add(model);
if (checkPoint.contains(i)) {
System.out.println();
// 偏向锁
System.out.println(ClassLayout.parseInstance(model).toPrintable() + "add----------------------" + i);
}
}
}
});
Runnable runnable = () -> {
for (int i = 0; i < list.size(); i++) {
Model model = list.get(i);
synchronized (model) {
if (checkPoint.contains(i)) {
System.out.println();
// 在 20 个之前都是轻量级锁,在 20 个以及之后是偏向锁
System.out.println(ClassLayout.parseInstance(model).toPrintable() + Thread.currentThread().getName() + "----------------------" + i);
}
}
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t0.start();
Thread.sleep(4000);
t1.setName("t1");
t1.start();
Thread.sleep(40000);
t2.setName("t2");
t2.start();
// t2 线程中发生重偏向现象
t2.join();
// 偏向锁
System.out.println(ClassLayout.parseInstance(new Model()).toPrintable() + "main----------------------");
}
}
批量撤销
如果我们在重偏向一次后,又进行了第二次重偏向,如下
public class BiasedLockDemo4 {
// 同步容器
public static List<Model> list = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
// 检查点
List<Integer> checkPoint = Arrays.asList(9,19,49);
Thread.sleep(5000);
// JVM 启动 4 秒后创建对象
Thread t0 = new Thread(() -> {
for (int i = 0; i < 50; i++) {
Model model = new Model();
synchronized (model) {
list.add(model);
if (checkPoint.contains(i)) {
System.out.println();
// 偏向锁
System.out.println(ClassLayout.parseInstance(model).toPrintable() + "add----------------------" + i);
}
}
}
});
Runnable runnable = () -> {
for (int i = 0; i < list.size(); i++) {
Model model = list.get(i);
synchronized (model) {
if (checkPoint.contains(i)) {
System.out.println();
// 在 20 个之前都是轻量级锁,在 20 个以及之后是偏向锁
System.out.println(ClassLayout.parseInstance(model).toPrintable() + Thread.currentThread().getName() + "----------------------" + i);
}
}
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t0.start();
Thread.sleep(4000);
t1.setName("t1");
t1.start();
Thread.sleep(4000);
t2.setName("t2");
t2.start();
// t2 线程中并没有发生重偏向现象
t2.join();
// 无锁,因为发生了批量撤销
System.out.println(ClassLayout.parseInstance(new Model()).toPrintable() + "main----------------------");
}
}
结果可见,是不会发生重偏向效果的,并且如果数量达到了 40 的阈值(同一线程内),就会发生批量撤销,新建的对象将直接是无锁状态而不再是匿名偏向状态,但是我们如果设置为 list 的大小为 30,即使我们增加一个 t3,也不会发生批量撤销,新建的 Model 对象就不会是无锁状态,而是偏向锁状态
注意
经历过 HashCode 计算后的对象,数组对象是无法作为偏向锁的
public class BiasedLockDemo6 {
public static void main(String[] args) throws InterruptedException {
// JVM 虚拟机启动的时候创建
Model[] model1 = new Model[]{new Model()};
// 无锁
System.out.println(ClassLayout.parseInstance(model1).toPrintable());
Thread.sleep(5000);
// JVM 启动 4 秒后创建对象
Model[] model2 = new Model[]{new Model()};
// 无锁
System.out.println(ClassLayout.parseInstance(model2).toPrintable());
}
}
public class BiasedLockDemo7 {
public static void main(String[] args) throws InterruptedException {
// JVM 虚拟机启动的时候创建
Model model1 = new Model();
// 无锁
System.out.println(ClassLayout.parseInstance(model1).toPrintable());
Thread.sleep(5000);
// JVM 启动 4 秒后创建对象
Model model2 = new Model();
model2.hashCode();
// 无锁
System.out.println(ClassLayout.parseInstance(model2).toPrintable());
}
}