偏向锁详解

偏向锁

概念

根据计算机的 二八理论 而言,临界区被访问时,有 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());
    }
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值