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

猜你喜欢

转载自blog.csdn.net/qq_31350373/article/details/80348126
今日推荐