java多線程設計之Producer-Consumer模式

生產者安全地將數據交給消費者

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模式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章