多線程訪問共享數據時,代碼結構通常分為兩類:
public class TicketSeller implements Runnable {
private int tickets = 100; // 共享數據
@Override
public void run() {
while (true) {
synchronized (this) { // 鎖住當前實例
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " 賣出第 " + tickets-- + " 張票");
} else {
break;
}
}
}
}
}
// 使用:同一實例傳給多線程
TicketSeller seller = new TicketSeller();
new Thread(seller, "窗口A").start();
new Thread(seller, "窗口B").start();
new Thread(seller, "窗口C").start();
關鍵點:單一 Runnable 實例封裝共享狀態,synchronized(this) 保證原子性。
// 共享數據封裝類
class Counter {
private int count = 0;
public synchronized void increment() { count++; }
public synchronized void decrement() { count--; }
public synchronized int get() { return count; }
}
// 增量任務
class IncrementTask implements Runnable {
private final Counter counter;
public IncrementTask(Counter counter) { this.counter = counter; }
@Override public void run() { for (int i=0;i<100;i++) counter.increment(); }
}
// 減量任務
class DecrementTask implements Runnable {
private final Counter counter;
public DecrementTask(Counter counter) { this.counter = counter; }
@Override public void run() { for (int i=0;i<100;i++) counter.decrement(); }
}
// 使用
Counter shared = new Counter();
new Thread(new IncrementTask(shared)).start();
new Thread(new DecrementTask(shared)).start();
public class SharedDataDemo {
private int sharedCount = 0; // 外部類字段
// 內部類自動持有外部類引用
class Increment implements Runnable {
@Override public void run() {
for (int i=0;i<100;i++) {
synchronized (SharedDataDemo.this) { sharedCount++; }
}
}
}
class Decrement implements Runnable {
@Override public void run() {
for (int i=0;i<100;i++) {
synchronized (SharedDataDemo.this) { sharedCount--; }
}
}
}
public void start() {
new Thread(new Increment()).start();
new Thread(new Decrement()).start();
}
}
Counter),由類內部負責同步,調用者無感知。final String、Enum、Guava ImmutableList),無需同步。ConcurrentHashMap、CopyOnWriteArrayList、AtomicInteger 等無鎖/細粒度鎖組件,減少手動同步。| 錯誤模式 | 後果 | 修正 |
|---|---|---|
| 不同 Runnable 實例,各自擁有獨立數據副本 | 數據不共享,線程操作各自副本 | 構造時注入同一共享對象引用 |
| 用非 final 變量在匿名內部類中 | 編譯錯誤或捕獲變量副本 | 用 final 或 efectively final 變量;或封裝為可變容器 |
| synchronized 鎖對象不一致 | 同步失效 | 確保所有線程鎖住同一對象(共享對象實例或 class 字面量) |
| 雙重檢查鎖定未加 volatile | 指令重排導致部分初始化對象可見 | 共享引用加 volatile |