在多線程程序中,線程間共享數據是常態。根據「執行代碼是否相同」與「如何傳遞共享對象」,主要分為三大類實現模式。掌握這些模式,能幫助你寫出線程安全、結構清晰的並發代碼。
適用場景:所有線程執行邏輯完全一致,如賣票系統、批量處理任務。
核心思想:創建 單一 Runnable 實例,多個 Thread 共享該實例,共享數據作為該實例的成員變量。
public class ShareRunnableDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData(); // 單一共享對象
new Thread(shareData).start(); // 線程 A
new Thread(shareData).start(); // 線程 B
}
static class ShareData implements Runnable {
private int count = 100;
@Override
public void run() {
while (count > 0) {
decrease(); // 同步方法保護共享變量
}
}
public synchronized void decrease() { // 方法鎖 = this (shareData 實例)
count--;
System.out.println(Thread.currentThread().getName() + " count: " + count);
}
}
}
關鍵點:synchronized 修飾實例方法,鎖對象是 shareData 實例本身,兩線程爭奪同一把鎖,實現互斥。
適用場景:線程職責不同(如生產者/消費者),但需操作同一份數據。
核心思想:將共享數據及其操作封裝在獨立類中,將該對象引用傳遞給各 Runnable 實例。這樣「數據與操作綁定」,便於實現封裝性、互斥與通信。
public class IndependentDataObjectDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData(); // 獨立共享數據對象
new Thread(new Decrease(shareData)).start(); // 消費者線程
new Thread(new Increment(shareData)).start(); // 生產者線程
}
// 消費者任務
static class Decrease implements Runnable {
private final ShareData shareData;
public Decrease(ShareData shareData) { this.shareData = shareData; }
@Override public void run() { shareData.decrease(); }
}
// 生產者任務
static class Increment implements Runnable {
private final ShareData shareData;
public Increment(ShareData shareData) { this.shareData = shareData; }
@Override public void run() { shareData.increment(); }
}
// 共享數據封裝類:數據 + 操作 + 同步控制
static class ShareData {
private int count = 100;
public synchronized void decrease() { count--; /* ... */ }
public synchronized void increment() { count++; /* ... */ }
}
}
優勢:職責清晰分離,ShareData 內聚了數據與同步邏輯,Runnable 實現類只負責任務調度,符合「高內聚、低耦合」。
適用場景:邏輯較簡單,不想創建額外類文件,將共享數據作為外部類成員,各任務作為內部類。
public class InnerClassDemo {
private static int count = 100; // 外部類靜態成員作為共享數據
public static void main(String[] args) {
new Thread(new Decrease()).start();
new Thread(new Increment()).start();
}
// 外部類提供同步操作方法
public static synchronized void decrease() { count--; /* ... */ }
public static synchronized void increment() { count++; /* ... */ }
// 內部類任務直接調用外部類方法
static class Decrease implements Runnable {
@Override public void run() { decrease(); }
}
static class Increment implements Runnable {
@Override public void run() { increment(); }
}
}
注意:此處鎖對象是 InnerClassDemo.class (靜態方法鎖類對象)。若用實例方法,鎖則是外部類實例。
結合模式二與三:共享數據封裝為獨立對象,任務用匿名內部類表達,代碼緊湊且結構清晰。
public class AnonymousInnerDemo {
public static void main(String[] args) {
final ShareData shareData = new ShareData(); // final 保證引用不變
new Thread(() -> { // Java 8 Lambda 簡化匿名內部類
while (true) shareData.decrease();
}).start();
new Thread(() -> {
while (true) shareData.increment();
}).start();
}
static class ShareData {
private int count = 100;
public synchronized void decrease() { count--; /* ... */ }
public synchronized void increment() { count++; /* ... */ }
}
}
| 模式 | 適用場景 | 鎖對象 | 推薦指數 |
|---|---|---|---|
| 共享 Runnable | 邏輯完全相同的線程組 | Runnable 實例 | ⭐⭐ |
| 獨立數據對象 | 線程職責不同,需共享數據 | 數據對象實例 | ⭐⭐⭐⭐⭐ (首選) |
| 內部類訪問外部成員 | 簡單邏輯、原型驗證 | 外部類實例/Class | ⭐⭐ |
| 匿名內部類 + 獨立對象 | 實際項目開發 | 數據對象實例 | ⭐⭐⭐⭐ |
核心原則:
ShareData),便於統一加鎖。final/Immutable 就不用鎖。AtomicInteger、ConcurrentHashMap、BlockingQueue 等 JUC 組件,減少手動同步錯誤。