基本協作
併發編程中有時需要線程間進行協作,典型的問題是生產者-消費者問題:有一個數據緩衝區,一個或多個生產者將數據存入這個緩衝區,一個或多個數據消費者將數據從緩衝區中取走。這裏有兩個問題要解決:
- 緩衝區是一個共享數據結構,必須使用同步機制控制對它的訪問,可以使用上一章節介紹的synchronized關鍵字進行同步。
- 緩衝區的數據是有限的,如果當緩衝區滿了生產者就不能再放入數據,如果緩衝區空了消費者就不能讀取數據。這是本文討論的生產者和消費者之間的協作問題。
Java裏線程協作的基本方式是通過Object對象提供的wait、notify和notifyAll方法,這三個方法都需要在同步代碼塊中調用,否則JVM將拋出IllegalMonitorStateException。當線程調用wait方法時,JVM將這個線程置入休眠,並且釋放控制這個同步代碼塊的對象,同時允許其他線程執行這個對象控制的其他同步代碼塊。爲了喚醒這個線程,必須在這個對象控制的某個同步代碼塊中調用notify或者notifyAll方法。
下面以生產者-消費者示例來說明synchronized、wait、notify、notifyAll的使用:
public class ProducerConsumerDemo {
public static void main(String[] args){
DataStorage storage = new DataStorage(10);
Runnable consumer = new Consumer(storage);
Runnable producer = new Producer(storage);
Thread pt1 = new Thread(producer);
Thread ct1 = new Thread(consumer);
System.out.println("main:啓動1個生產者");
pt1.start();;
System.out.println("main:啓動1個消費者");
ct1.start();
System.out.println("main:等待線程完成");
try {
pt1.join();
ct1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main:退出");
}
}
/** 消費者**/
class Consumer implements Runnable{
private DataStorage storage;
public Consumer(DataStorage storage){
this.storage = storage;
}
@Override
public void run() {
for(int i=0; i<20; i++){
storage.get();
}
}
}
/** 生產者**/
class Producer implements Runnable{
private DataStorage storage;
public Producer(DataStorage storage){
this.storage = storage;
}
@Override
public void run() {
for(int i=0; i<20; i++){
storage.put();
}
}
}
/** 公共數據存儲類 **/
class DataStorage{
private int maxSize;
private List<Date> storage;
public DataStorage(int max){
this.maxSize = max;
this.storage = new LinkedList<Date>();
}
public synchronized void put(){
if(this.storage.size() == maxSize){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.storage.add(new Date());
System.out.println("生產。數據數量:" + storage.size());
notifyAll();
}
public synchronized Date get(){
if(this.storage.size() == 0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Date data = ((LinkedList<Date>)this.storage).poll();
System.out.println("消費。數據數量:" + storage.size());
notifyAll();
return data;
}
}
程序運行日誌:
main:啓動1個生產者
main:啓動1個消費者
main:等待線程完成
生產。數據數量:1
生產。數據數量:2
生產。數據數量:3
生產。數據數量:4
生產。數據數量:5
生產。數據數量:6
生產。數據數量:7
生產。數據數量:8
生產。數據數量:9
生產。數據數量:10
消費。數據數量:9
消費。數據數量:8
消費。數據數量:7
消費。數據數量:6
消費。數據數量:5
消費。數據數量:4
消費。數據數量:3
消費。數據數量:2
消費。數據數量:1
消費。數據數量:0
生產。數據數量:1
消費。數據數量:0
生產。數據數量:1
消費。數據數量:0
生產。數據數量:1
生產。數據數量:2
生產。數據數量:3
生產。數據數量:4
生產。數據數量:5
生產。數據數量:6
生產。數據數量:7
生產。數據數量:8
消費。數據數量:7
消費。數據數量:6
消費。數據數量:5
消費。數據數量:4
消費。數據數量:3
消費。數據數量:2
消費。數據數量:1
消費。數據數量:0
main:退出