偏向鎖
概念
根據計算機的 二八理論 而言,臨界區被訪問時,有 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());
}
}