在JDK的官方的wait()方法的注釋中明確表示線程可能被“虛假喚醒“,JDK也明確推薦使用while來判斷狀態信息。那么這種情況的發生的可能性有多大呢?
使用生產者消費者模型來說明,偽喚醒造成的后果是本來未被喚醒的線程被喚醒了,那么就破壞了生產者消費者中的判斷條件,也就是例子中的while條件number == 0或者number == 1。最終導致的結果就死0和1不能交替出現。
JDK的兩種同步方案均可能出現這種偽喚醒的問題(API說明明確表示會出現這種現象),這兩種組合是synchronized+wait+notify和ReentrantLock+await+signal。下面的例子中,如果把while換成if,那么就0、1就不能交替出現,反制則會,例子中是100個線程進行增加,100個線程進行減少。
在Java並發編程書上面引用了Thinking In Java的一句話說,大概意思是:任何並發編程都是通過加鎖來解決。其實JDK並發編程也是通過加鎖解決,每個對象都有一個對象鎖,並且有一個與這個鎖相關的隊列,來實現並發編程。區別在於加鎖的粒度問題,讀讀可以並發(在讀遠大於寫的場景下比較合適),其他三種情況不能並發。而關系型數據庫也是利用這一思想,只不過做得更加徹底,除了寫寫不能並發,其他三種情況都能並發,這得益於MVCC模型。
public class Resource { public int number = 0; public synchronized void add() { while (number == 1) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } number++; System.err.println(number + "-" + Thread.currentThread().getId()); notifyAll(); } public synchronized void minus() { while (number == 0) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } number--; System.err.println(number + "-" + Thread.currentThread().getId()); notifyAll(); } public static class AddThread implements Runnable { private Resource resource; public AddThread(Resource resource) { this.resource = resource; } @Override public void run() { for (;;) resource.add(); } } public static class MinusThread implements Runnable { private Resource resource; public MinusThread(Resource resource) { this.resource = resource; } @Override public void run() { for (;;) resource.minus(); } } public static void main(String[] args) { Resource resource = new Resource(); for (int i = 0; i < 100; i++) { new Thread(new AddThread(resource)).start(); new Thread(new MinusThread(resource)).start(); } } }
public class ResourceLock { private int number = 0; private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); public int getNumber() { return this.number; } public void increase() { lock.lock(); try { while (number == 1) { condition.await(); } number++; System.err.println(number + "-" + Thread.currentThread().getId()); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } public void decrease() { lock.lock(); try { while (number == 0) { condition.await(); } number--; System.err.println(number + "-" + Thread.currentThread().getId()); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } static class IncreaseThread implements Runnable { private ResourceLock resource; public IncreaseThread(ResourceLock resource) { this.resource = resource; } @Override public void run() { for (;;) resource.increase(); } } static class DecreaseThread implements Runnable { private ResourceLock resource; public DecreaseThread(ResourceLock resource) { this.resource = resource; } @Override public void run() { for (;;) resource.decrease(); } } public static void main(String[] args) throws Exception { ResourceLock resource = new ResourceLock(); ExecutorService es = Executors.newFixedThreadPool(5); for (int i = 0; i < 100; i++) { es.submit(new IncreaseThread(resource)); es.submit(new DecreaseThread(resource)); } } }