在多線程操作中,我們常常會遇到需要先判斷信號量狀態是否就緒,然后執行后續操作的場景。這里對狀態的判斷使用的是while而不是單線程下常用的if。
以下示例展示了一個簡單的生產者-消費者模型:當隊列滿的時候,阻塞set;當隊列為空的時候,阻塞get操作。
public class EventStorage { private int maxSize; private List<Date> storage; public EventStorage(){ maxSize=10; storage=new LinkedList<>(); } public synchronized void set(){ while (storage.size()==maxSize){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } storage.offer(new Date()); System.out.printf("Set: %d",storage.size()); notifyAll(); } public synchronized void get(){ while (storage.size()==0){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.printf("Get: %d: %s",storage. size(),((LinkedList<?>)storage).poll()); notifyAll(); } } public class Producer implements Runnable { private EventStorage storage; public Producer(EventStorage storage){ this.storage=storage; } @Override public void run() { for (int i=0; i<100; i++){ storage.set(); } } } public class Consumer implements Runnable { private EventStorage storage; public Consumer(EventStorage storage){ this.storage=storage; } @Override public void run() { for (int i=0; i<100; i++){ storage.get(); } } } public class Main { public static void main(String[] args) { EventStorage storage=new EventStorage(); Producer producer=new Producer(storage); Thread thread1=new Thread(producer); Consumer consumer=new Consumer(storage); Thread thread2=new Thread(consumer); thread1.start(); thread2.start(); } }
在set中使用了
while (storage.size()==maxSize){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
在get中使用了
while (storage.size()==0){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
原因:
在線程中notify或者notifyAll會喚醒一個或多個線程,當線程被喚醒后,被喚醒的線程繼續執行阻塞后的操作。
這里分析一下get操縱: 當某個線程得到鎖時storage為空,此時它應該wait,下次被喚醒時(任意線程調用notify),storage可能還是空的。因為有可能其他線程清空了storage。如果此時用的是if它將不再判斷storage是否為空,直接繼續,這樣就引起了錯誤。但如果用while則每次被喚醒時都會先檢查storage是否為空再繼續,這樣才是正確的操作;生產也是同一個道理。