在多線程操作中,我們常常會遇到需要先判斷信號量狀態是否就緒,然後執行後續操作的場景。這裏對狀態的判斷使用的是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();
}
}
while (storage.size()==maxSize){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
while (storage.size()==0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
原因:
在線程中notify或者notifyAll會喚醒一個或多個線程,當線程被喚醒後,被喚醒的線程繼續執行阻塞後的操作。
這裏分析一下get操縱: 當某個線程得到鎖時storage爲空,此時它應該wait,下次被喚醒時(任意線程調用notify),storage可能還是空的。因爲有可能其他線程清空了storage。如果此時用的是if它將不再判斷storage是否爲空,直接繼續,這樣就引起了錯誤。但如果用while則每次被喚醒時都會先檢查storage是否爲空再繼續,這樣纔是正確的操作;生產也是同一個道理。