生產者安全地將數據交給消費者。
producer是生產者的意思:指生產數據的線程,consumer是消費者的意思,指的是使用數據的線程。
例如消費者想要獲取數據,可數據還沒生成,或者生成者想要交付數據,而消費者的狀態還無法接受數據這樣的情況。這個時候Producer-Consumer模式在生產者和消費者之間加入了一個“橋樑角色”,該橋樑角色用於消除線程間處理速度的差異。
這是一個實例圖,方便理解Producer-Consumer模式:
下面來看一個實例:
MakerThread用於製作蛋糕,並將其放入到桌上,也就是糕點師
public class MakerThread extends Thread{
private final Random random;
private final Table table;
private static int id = 0;//蛋糕的流水號(所有糕點師共用)
public MakerThread(String name,Table table,long seed){
super(name);
this.table = table;
this.random = new Random(seed);
}
public void run(){
try {
while(true){
Thread.sleep(random.nextInt(1000));
String cake = "[Cake No."+nextId()+" by "+getName()+"]";
table.put(cake);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static synchronized int nextId(){
return id++;
}
}
EaterThread類用於表示從桌子上取蛋糕吃的客人
public class EaterThread extends Thread{
private final Random random;
private final Table table;
public EaterThread(String name,Table table,long seed){
super(name);
this.table = table;
this.random = new Random(seed);
}
public void run(){
try {
while(true){
String cake = table.take();
Thread.sleep(random.nextInt(1000));
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Table用於表示放置蛋糕的桌子。
String[] buffer,存放蛋糕的數組
tail:表示下一次放置蛋糕的位置
head:表示下一次取蛋糕的位置
count:表示當前桌子上放置的蛋糕數
public class Table {
private final String[] buffer;
private int tail;//下次put的位置
private int head;//下次take的位置
private int count;//buffer中的蛋糕個數
public Table(int count) {
this.buffer = new String[count];
this.head = 0;
this.tail = 0;
this.count = 0;
}
//放置蛋糕
public synchronized void put(String cake) throws InterruptedException{
System.out.println(Thread.currentThread().getName()+" puts "+cake);
while(count >= buffer.length){
wait();
}
buffer[tail] =cake;
tail = (tail+1) % buffer.length;
count++;
notifyAll();
}
//拿取蛋糕
public synchronized String take() throws InterruptedException{
while(count<=0){
wait();
}
String cake = buffer[head];
head = (head+1)%buffer.length;
count--;
notifyAll();
System.out.println(Thread.currentThread().getName()+"takes "+cake);
return cake;
}
}
實例執行:
public static void main(String[] args) {
Table table = new Table(3);
new MakerThread("Maker-1", table, 30000).start();
new MakerThread("Maker-2", table, 90000).start();
new MakerThread("Maker-3", table, 50000).start();
new EaterThread("Eater-1", table, 30000).start();
new EaterThread("Eater-2", table, 60000).start();
new EaterThread("Eater-3", table, 30000).start();
}
運行結果:
Maker-3 puts [Cake No.0 by Maker-3]
Eater-3takes [Cake No.0 by Maker-3]
Maker-2 puts [Cake No.1 by Maker-2]
Eater-1takes [Cake No.1 by Maker-2]
Maker-1 puts [Cake No.2 by Maker-1]
Eater-2takes [Cake No.2 by Maker-1]
實例示意圖:
Product_Consumer的角色:
Data,Producer(生產者),Consumer(消費者),Channel(生產者和消費者出傳遞data的橋樑)
如上是角色的關係圖
在Producer-Consumer模式中,承擔安全責任的是Channel角色,Channel角色執行線程之間的互斥處理
當Producer角色將data角色傳遞給Channel角色時,如果Channel角色的狀態不適合接收Data,那麼Producer角色將一直等待,直到Channel角色的狀態變爲可以接收爲止。Consumer角色從Channel角色獲取Data角色,同理。
Channel的作用:
線程的協調運行要考慮“放在中間的東西”
線程的互斥處理要考慮“應該保護的東西”
理解InterruptedException異常
可能會花費時間,但可以取消
加了throws InteruotedException的方法
wait(),sleep(),join()
花費時間的方法:
wait(),sleep(),join()需要花費時間來等待被notify/notifyAll指定時間,指定線程終止,確實是“花費時間”的方法。
interrupt方法可以終止sleep線程的暫停狀態,調用sleep的這個線程會拋出InteruotedException異常
wait方法也是可以被interrupt方法取消,但是這個線程會獲取鎖之後才拋出異常。
interrupt取消這些線程,並非是它讓調用對象的線程產生拋異常,而是它僅僅改變了他們的中斷狀態
isInterrupted方法用於檢查中斷狀態(並不會改變中斷狀態)
java.util.concurrent.Exchanger類交換緩存區
用於讓兩個線程安全地交換對象
下面看實例
ProducerThread:
填充字符,直至緩衝區被填滿
使用exchange方法將填滿的緩衝區傳遞給ConsumerThread
傳遞緩衝區,作爲交換,接收空的緩衝區
public class ProducerThread extends Thread{
private final Exchanger<char[]> exchanger;
private char[] buffer = null;
private char index = 0;
private final Random random;
public ProducerThread(Exchanger<char[]> exchanger, char[] buffer,long seed) {
this.exchanger = exchanger;
this.buffer = buffer;
this.random = new Random(seed);
}
public void run(){
try {
while(true){
for(int i=0;i<buffer.length;i++){
buffer[i] = nextChar();
System.out.println(Thread.currentThread().getName()+" : "+buffer[i]+" -> ");
}
//交換緩衝區
System.out.println(Thread.currentThread().getName()+" :before Exchange");
buffer = exchanger.exchange(buffer);
System.out.println(Thread.currentThread().getName()+ " :after Exchange");
for(int i=0;i<buffer.length;i++){
System.out.println(Thread.currentThread().getName()+" : "+buffer[i]+" -> ");
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private char nextChar() throws InterruptedException{
char c = (char) ('A'+index % 26);
index++;
Thread.sleep(random.nextInt(1000));
return c;
}
}
ConsumerThread循環執行如下操作
使用exchange方法將空的緩衝區傳遞給ProducerThread
傳遞空的緩衝區後,作爲交換,接收被填滿字符的緩衝區
使用緩衝區中的字符(顯示)
public class ConsumerThread extends Thread{
private final Exchanger<char[]> exchanger;
private char[] buffer = null;
private final Random random;
public ConsumerThread(Exchanger<char[]> exchanger, char[] buffer,long seed) {
super();
this.exchanger = exchanger;
this.buffer = buffer;
this.random = new Random(seed);
}
public void run(){
try {
while(true){
System.out.println(Thread.currentThread().getName()+" :before exchange");
buffer = exchanger.exchange(buffer);
System.out.println(Thread.currentThread().getName()+" :after exchange");
for(int i =0;i< buffer.length;i++){
System.out.println(Thread.currentThread().getName() +" :->"+buffer[i]);
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Main類:首先將buffer1緩衝區傳給ProducerThread,然後將buffer2緩衝區傳給ConsumerThread,同時還會將通用的Exchanger的實例分別傳給ProducerThread 和ConsumerThread
public class Main {
public static void main(String[] args) {
Exchanger<char[]> exchanger = new Exchanger<char[]>();
char[] buffer1 = new char[10];
char[] buffer2 = new char[10];
new ProducerThread(exchanger, buffer1, 300000).start();;
new ConsumerThread(exchanger, buffer2, 200000).start();;
}
}
Exchanger交換緩衝區圖
Producer-Consumer總結
由於將有多個線程使用Channel角色,所有我們需要在Channel角色中執行互斥處理。在Channel角色中,從Producer角色獲取Data角色的部分和向Consumer傳遞Data的部分,都使用了Guarded Suspension(滿足條件的獲取或傳遞),這樣,線程之間可以安全地進行通信。如果Channel角色可存儲的Data角色數量足夠多,那麼便可以緩解Producer角色和Consumer角色之間處理速度的差異
這就是Producer-Consumer模式