JAVA鎖機制:對象鎖與類鎖
在多線程編程中,合理使用鎖機制是保證數據一致性和線程安全的關鍵。本文將通過示例詳細講解 Java 中的對象鎖和類鎖的原理、用法及區別。
一、未加鎖的并發問題
先看一段未加鎖的代碼:
public class SynchronizedTest {private int shareField = 0;public void add() {shareField++;System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前的shareField為:" + shareField);}public static void main(String[] args) {SynchronizedTest sync = new SynchronizedTest();new Thread(() -> {for (int n = 0; n < 100000; n++) {sync.add();}}).start();new Thread(() -> {for (int n = 0; n < 100000; n++) {sync.add();}}).start();}
}
上述代碼啟動兩個線程,每個線程各自循環10萬次,對 shareField
進行自增。理論上,最終 shareField
應為 200000,但實際運行結果往往小于 200000:
當前線程:Thread-0 當前的shareField為:199994
當前線程:Thread-0 當前的shareField為:199995
...
原因在于多個線程并發修改同一變量,導致數據競爭。
二、對象鎖
對象鎖用于保護同一個實例的資源,確保同一時刻只有一個線程能訪問被鎖定的代碼塊。
1. 鎖定非靜態方法
public class SynchronizedTest {private int shareField = 0;public synchronized void add() {shareField++;System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前的shareField為:" + shareField);}// 省略 main 方法
}
synchronized
修飾非靜態方法時,鎖的是當前實例對象 (this
)。
2. 鎖定 this 對象(代碼塊)
有時只需對方法中的部分代碼加鎖,可以使用同步代碼塊:
public void add() {synchronized (this) {shareField++;System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前的shareField為:" + shareField);}
}
3. 鎖定特定對象
也可以指定其他對象作為鎖:
private final Object obj = new Object();
public void add() {synchronized (obj) {shareField++;System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前的shareField為:" + shareField);}
}
對象鎖特點
- 鎖對象:當前實例(
this
)或指定對象 - 多線程訪問同一實例的同步方法時互斥
- 不同實例之間互不影響
三、類鎖
類鎖用于保護類級別的資源(如靜態變量),確保同一時刻只有一個線程能訪問被鎖定的靜態資源。
1. 鎖定靜態方法
public static synchronized void add() {shareField++;System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前的shareField為:" + shareField);
}
synchronized
修飾靜態方法時,鎖的是類的 Class
對象。
2. 鎖定 class 對象
public void add() {synchronized (SynchronizedTest.class) {shareField++;System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前的shareField為:" + shareField);}
}
3. 鎖定靜態實例變量
private static final Object obj = new Object();
public void add() {synchronized (obj) {shareField++;System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前的shareField為:" + shareField);}
}
類鎖特點
- 鎖對象:類的
Class
對象或靜態實例變量 - 所有實例共享同一把鎖,實現全局互斥
四、對象鎖與類鎖的區別
選擇對象鎖還是類鎖,取決于需要保護的變量是實例級還是類級(靜態)。
例如:
public class SynchronizedTest {private static int shareField = 0;public void add() {synchronized (this) {shareField++;System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前的shareField為:" + shareField);}}public static void main(String[] args) {new Thread(() -> {SynchronizedTest sync1 = new SynchronizedTest();for (int n = 0; n < 100000; n++) {sync1.add();}}).start();new Thread(() -> {SynchronizedTest sync2 = new SynchronizedTest();for (int n = 0; n < 100000; n++) {sync2.add();}}).start();}
}
上述代碼中,兩個線程分別操作不同實例,但都修改靜態變量 shareField
。此時對象鎖無法保證線程安全,需使用類鎖:
public void add() {synchronized (SynchronizedTest.class) {shareField++;System.out.println("當前線程:" + Thread.currentThread().getName() + " 當前的shareField為:" + shareField);}
}
五、總結
特性 | 對象鎖 | 類鎖 |
---|---|---|
鎖對象 | 當前實例(this)/obj | 類的Class對象/靜態實例變量 |
作用范圍 | 同一實例間互斥 | 所有實例間互斥 |
適用場景 | 保護實例級變量 | 保護類級變量(靜態變量) |
并發影響 | 不同實例間無互斥 | 所有實例共享同一把鎖 |
實現方式 | synchronized方法/代碼塊 | static synchronized方法/class鎖對象 |
合理選擇鎖的類型,是實現高效并發和線程安全的關鍵。