1. 前言
隨着摩爾定律的失效,Amdahl定律成爲了多核計算機性能發展的指導。對於現在的java程序員們來說,併發編程越來越重要和習以爲常。很慚愧和恐慌的是我對java的併發編程一直是隻知道概念,入門都不算。最近工作需要,開始認真學習java併發編程。先找了一本簡單的電子書《Java7併發編程實戰手冊》開始看。剛剛看到簡單的生產者消費者問題,在書中給出的代碼中,有一點不理解:爲什麼wait()語句要放在while循環之內?經過網上搜索以及翻看《effective java》第二版。終於明白了一些。特此記錄下來。
2. 生產者消費者代碼
生產者消費者代碼如下:
數據存儲類:EventStorage:(get和set標記爲同步方法,並使用了wait和notify機制)
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
/**
* This class implements an Event storage. Producers will storage
* events in it and Consumers will process them. An event will
* be a java.util.Date object
*
*/
public class EventStorage {
/**
* Maximum size of the storage
*/
private int maxSize;
/**
* Storage of events
*/
private List<Date> storage;
/**
* Constructor of the class. Initializes the attributes.
*/
public EventStorage(){
maxSize=10;
storage=new LinkedList<>();
}
/**
* This method creates and storage an event.
*/
public synchronized void set(){
while (storage.size()>=maxSize){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.add(new Date());
System.out.printf("Set: %d\n",storage.size());
notify();
}
/**
* This method delete the first event of the storage.
*/
public synchronized void get(){
while (storage.size()==0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("Get: %d: %s\n",storage.size(),((LinkedList<?>)storage).poll());
notify();
}
}
生產者類:Producer:(調用EventStorage類中的set方法存入數據)
/**
* This class implements a producer of events.
*
*/
public class Producer implements Runnable {
/**
* Store to work with
*/
private EventStorage storage;
/**
* Constructor of the class. Initialize the storage.
* @param storage The store to work with
*/
public Producer(EventStorage storage){
this.storage=storage;
}
/**
* Core method of the producer. Generates 100 events.
*/
@Override
public void run() {
for (int i=0; i<100; i++){
storage.set();
}
}
}
消費者類:Consumer:(調用EventStorage類中的get方法取出數據)
/**
* This class implements a consumer of events.
*
*/
public class Consumer implements Runnable {
/**
* Store to work with
*/
private EventStorage storage;
/**
* Constructor of the class. Initialize the storage
* @param storage The store to work with
*/
public Consumer(EventStorage storage){
this.storage=storage;
}
/**
* Core method for the consumer. Consume 100 events
*/
@Override
public void run() {
for (int i=0; i<100; i++){
storage.get();
}
}
}
主類:Main:(分別啓動一個生產者和一個消費者線程)
/**
* Main class of the example
*/
public class Main {
/**
* Main method of the example
*/
public static void main(String[] args) {
// Creates an event storage
EventStorage storage=new EventStorage();
// Creates a Producer and a Thread to run it
Producer producer=new Producer(storage);
Thread thread1=new Thread(producer);
// Creates a Consumer and a Thread to run it
Consumer consumer=new Consumer(storage);
Thread thread2=new Thread(consumer);
// Starts the thread
thread2.start();
thread1.start();
}
}
運行截圖如下所示:
3. 永遠不要在循環之外調用wait方法
《Effective Java》第二版中文版第69條244頁位置對這一點說了一頁,我看着一知半解。我能理解的一點是:對於從wait中被notify的進程來說,它在被notify之後還需要重新檢查是否符合執行條件,如果不符合,就必須再次被wait,如果符合才能往下執行。所以:wait方法應該使用循環模式來調用。按照上面的生產者和消費者問題來說:錯誤情況一:如果有兩個生產者A和B,一個消費者C。當存儲空間滿了之後,生產者A和B都被wait,進入等待喚醒隊列。當消費者C取走了一個數據後,如果調用了notifyAll(),注意,此處是調用notifyAll(),則生產者線程A和B都將被喚醒,如果此時A和B中的wait不在while循環中而是在if中,則A和B就不會再次判斷是否符合執行條件,都將直接執行wait()之後的程序,那麼如果A放入了一個數據至存儲空間,則此時存儲空間已經滿了;但是B還是會繼續往存儲空間裏放數據,錯誤便產生了。錯誤情況二:如果有兩個生產者A和B,一個消費者C。當存儲空間滿了之後,生產者A和B都被wait,進入等待喚醒隊列。當消費者C取走了一個數據後,如果調用了notify(),則A和B中的一個將被喚醒,假設A被喚醒,則A向存儲空間放入了一個數據,至此空間就滿了。A執行了notify()之後,如果喚醒了B,那麼B不會再次判斷是否符合執行條件,將直接執行wait()之後的程序,這樣就導致向已經滿了數據存儲區中再次放入數據。錯誤產生。
下面是錯誤情況二的演示代碼。根據第二節的代碼修改而來:
數據存儲類:EventStorage:(set中使用if代替while判斷執行條件)
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
/**
* This class implements an Event storage. Producers will storage
* events in it and Consumers will process them. An event will
* be a java.util.Date object
*
*/
public class EventStorage {
/**
* Maximum size of the storage
*/
private int maxSize;
/**
* Storage of events
*/
private List<Date> storage;
/**
* Constructor of the class. Initializes the attributes.
*/
public EventStorage(){
maxSize=10;
storage=new LinkedList<>();
}
/**
* This method creates and storage an event.
*/
public synchronized void set(){
if (storage.size()>=maxSize){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.add(new Date());
System.out.printf("Set: %d\n",storage.size());
notify();
}
/**
* This method delete the first event of the storage.
*/
public synchronized void get(){
while (storage.size()==0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("Get: %d: %s\n",storage.size(),((LinkedList<?>)storage).poll());
notify();
}
}
生產者類:Producer:(調用EventStorage類中的set方法存入數據,沒有修改)
消費者類:Consumer:(調用EventStorage類中的get方法取出數據,在run方法中加入了一個1ms的休眠)
import java.util.concurrent.TimeUnit;
/**
* This class implements a consumer of events.
*
*/
public class Consumer implements Runnable {
/**
* Store to work with
*/
private EventStorage storage;
/**
* Constructor of the class. Initialize the storage
* @param storage The store to work with
*/
public Consumer(EventStorage storage){
this.storage=storage;
}
/**
* Core method for the consumer. Consume 100 events
*/
@Override
public void run() {
for (int i=0; i<100; i++){
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
storage.get();
}
}
}
主類:Main:(啓動了兩個生產者線程和一個消費者線程)
/**
* Main class of the example
*/
public class Main {
/**
* Main method of the example
*/
public static void main(String[] args) {
// Creates an event storage
EventStorage storage=new EventStorage();
// Creates a Producer and a Thread to run it
Producer producer=new Producer(storage);
Thread thread1=new Thread(producer);
Thread thread3=new Thread(producer);
// Creates a Consumer and a Thread to run it
Consumer consumer=new Consumer(storage);
Thread thread2=new Thread(consumer);
// Starts the thread
thread2.start();
thread1.start();
thread3.start();
}
}
程序運行截圖如下:說明存儲數據區已經錯誤的存儲了超過規定的最大存儲量的數據。併發錯誤。
寫在最後
像我一樣的老程序員們,醒醒吧,學習學習java.util.concurrent包吧;學習學習java7和java8的新特性吧。再不學習,我們就要被淘汰了!
參考資料:
《Java7併發編程實戰手冊》
《Effective Java》第二版中文版