在 Java 多線程編程中,鎖機制是實現線程同步、保證數據一致性的基石。本文將通過三個經典代碼示例,深入淺出地講解 Java 中 synchronized 關鍵字的使用方式、對象鎖與類鎖的區別,以及鎖的釋放時機。
首先看一個沒有任何同步措施的例子,多個線程並發訪問共享資源時會發生什麼:
public class ThreadTest extends Thread {
private int threadNo;
public ThreadTest(int threadNo) {
this.threadNo = threadNo;
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new ThreadTest(i).start();
}
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("Thread-" + threadNo + ": " + i);
}
}
}
現象:多個線程的輸出順序完全不可預期,交替輸出,甚至出現數據混亂。這就是典型的「線程不安全」。
通過引入一把「鎖對象」,確保同一時刻只有一個線程能執行關鍵代碼區:
public class ThreadTest2 extends Thread {
private int threadNo;
private String lock;
public ThreadTest2(int threadNo, String lock) {
this.threadNo = threadNo;
this.lock = lock;
}
public static void main(String[] args) {
String lock = new String(); // 共享鎖對象
for (int i = 0; i < 5; i++) {
new ThreadTest2(i, lock).start();
}
}
@Override
public void run() {
synchronized (lock) { // 獲取鎖
for (int i = 0; i < 5; i++) {
System.out.println("Thread-" + threadNo + ": " + i);
}
} // 釋放鎖
}
}
關鍵點:
synchronized(lock) 要求所有線程使用同一個鎖對象實例。很多開發者誤以為在方法上加 synchronized 就能解決所有問題,但這裡有個大坑:
public class ThreadTest3 extends Thread {
private int threadNo;
public ThreadTest3(int threadNo) {
this.threadNo = threadNo;
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
new ThreadTest3(i).start();
Thread.sleep(10); // 嘗試錯開啟動時間
}
}
// 這是一個實例方法,鎖的是 this 對象
public synchronized void abc(int threadNo) {
for (int i = 0; i < 5; i++) {
System.out.println("Thread-" + threadNo + ": " + i);
}
}
@Override
public void run() {
abc(threadNo);
}
}
為什麼仍然無效?
每次 new ThreadTest3(i) 都創建了一個新的對象實例,每個線程持有的 this 都不一樣,所以鎖的是不同的對象,根本起不到互斥作用!
如果想讓所有實例共享一把鎖,必須鎖住 Class 對象:
public static synchronized void staticSyncMethod() {
// 鎖的是 ThreadTest3.class
}
public void instanceSyncMethod() {
synchronized (ThreadTest3.class) {
// 同樣鎖住類對象
}
}
| 鎖類型 | 鎖對象 | 適用場景 | 注意事項 |
|---|---|---|---|
| 對象鎖 | this / 自定義對象 | 實例方法、代碼塊 | 必須確保多線程共享同一實例 |
| 類鎖 | ClassName.class | 靜態方法、跨實例同步 | 粒度較大,影響並發度 |
核心原則:
tryLock 超時機制。掌握鎖機制的本質,是寫出高性能、線程安全 Java 代碼的關鍵。