多執行緒共享資源:寫寫互斥、讀寫互斥、讀讀共存。Java 5+ 提供 ReentrantReadWriteLock,但若僅用 synchronized 如何實現?
Business 封裝 read()/write()。write() 直接 synchronized 保證互斥。read() 使用計數器 readThreads + 同步區塊協調:synchronized(this) { readThreads++; }。synchronized(this) { readThreads--; notifyAll(); }。write() 中 while (readThreads != 0) wait(); 等待所有讀者離開。
package communication;
import java.util.Random;
public class ReadWriteLockTest {
public static void main(String[] args) {
final Business business = new Business();
for (int i = 1; i <= 2; i++) {
new Thread(() -> {
for (int j = 1; j < 1000; j++) {
business.read();
sleep(900);
}
}).start();
new Thread(() -> {
Random r = new Random();
for (int j = 1; j < 1000; j++) {
business.write(r.nextInt(100));
sleep(1000);
}
}).start();
}
}
static void sleep(long ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { e.printStackTrace(); } }
}
class Business {
private int data = 0;
private int readThreads = 0;
public void read() {
synchronized (this) { readThreads++; }
System.out.println(Thread.currentThread().getName() + " read begin");
System.out.println(Thread.currentThread().getName() + " read: " + data);
System.out.println(Thread.currentThread().getName() + " read finish");
synchronized (this) { readThreads--; this.notifyAll(); }
}
public synchronized void write(int i) {
while (readThreads != 0) {
try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println(Thread.currentThread().getName() + " write start");
data = i;
System.out.println(Thread.currentThread().getName() + " write: " + i);
System.out.println(Thread.currentThread().getName() + " write over");
this.notifyAll();
}
}
readThreads++ 同步區 → 寫者被 synchronized(write) 阻塞。while(readThreads!=0) wait() 等待。notifyAll → 寫者被喚醒,若仍有其他讀者則再次 wait。notifyAll → 讀者與寫者競爭鎖,寫者若得鎖則讀者阻塞在 readThreads++。readThreads++ 同步區。結論:讀寫互斥、寫寫互斥、讀讀並發 目標達成。
當讀者頻繁、持續進入(readThreads 長期 > 0),寫者將永遠在 wait() 中,導致寫者飢餓。
純 synchronized 可實現基礎讀寫鎖,但飢餓、可重入、條件變數等進階特性需大量樣板碼。實務上直接使用 java.util.concurrent.locks 成熟實現,手工實現僅供教學驗證併發原理。