偏向鎖詳解

偏向鎖

概念

根據計算機的 二八理論 而言,臨界區被訪問時,有 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());
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章