Producer-Consumer线程模式的简单

812 阅读5分钟
Producer-Consumer
线程模式的简单介绍
Producer
是“生产者”的意思,指的是生成数据的线程,
Consumer
则是“消费者”的意思,指的是使用数据的线程。
生产者安全地将数据交给消费者。虽然仅是这样看似简单的操作,但当生产者和消费者以不同的线程运行时,两者之间的处理速度差异便会引起问题。例如,消费者想要获取数据,可数据还没生成,或者生产者想要交付数据,而消费者的状态还无法接收数据等。
Producer-Consumer
模式在生产者和消费者之间加入了一个“桥梁角色”
,
该桥梁角色
用于消除线程间处理速度的差异。
一般来说,在该模式中,生产者和消费者都有多个,当然生产者和消费者有时也会只有一个。当两者都只有一个时,我们称之为
Pipe
模式。
为了理解
Producer-Consumer
模式,我们先来看一个示例程序,在这个示例程序中,有
3
位糕点师制作蛋糕并将其放到桌子上,然后有
3
位客人来吃这些蛋糕。程序如下。
先理清需求:
(1)
糕点师
(MakerThread)
制作蛋糕
(String),
并将其放置到桌子
(Table)
(2)
桌子上最多可放置
3
个蛋糕
(3)
如果桌子上已经放满
3
个蛋糕时糕点师还要再放置蛋糕,必须等桌子上空出位置
(4)
客人
(EaterThread)
取桌子上的蛋糕吃
(5)客人按蛋糕被放置到桌子上的顺序来取蛋糕
(6)
当桌子上
1
个蛋糕都没有时,客人若要取蛋糕,必须等到桌子上新放置了蛋糕
Main
类会创建一个桌子上的实例,并启动表示糕点师和客人的线程。
MakerThread
EaterThread
的构造函数中传入的数字只是用来作为随机数的种子,数值本身并没有什么特别的意思。
public class Main{
public static void main(String[] args)
{
Table table = new Table(3); //
创建一个能放置
3
个蛋糕的桌子
new MakerThread("MakerThread-1",table,31415).start();
new MakerThread("MakerThread-2",table,92653).start();
new MakerThread("MakerThread-3",table,58979).start();
new EaterThread("EaterThread-1",table,32384).start();
new EaterThread("EaterThread-2",table,62643).start();
new EaterThread("EaterThread-3",table,38327).start();
}
}
MakerThread
类用于制作蛋糕,并将其放到桌子上,也就是糕点师,为了简单起见,我们像下面这样以
“流水号”和制作该蛋糕的“线程名称”来表示蛋糕。
为了使程序的运行结果方便查看,蛋糕的流水号在所有的糕点师之间是共用的。为此,这里将流水号(id)
声明为静态字段。
MakerThread
会先暂停一段随机长
(0
1000
毫秒之间
)
的时间,然后再调用
Table
类的
Put
方法将制作好的蛋糕放置到桌子上。暂停的这段时间模拟的是“制作蛋糕所花费的时间”。
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){
}
}
private static synchronized int nextId(){
return id++;
}
}
EaterThread
类用于表示从桌子上取蛋糕吃的客人
(
虽说是“吃”,其实这里只是执行
sleep
操作
)
。客人通过
Table
类的
take
方法取桌子上的蛋糕。然后,与
MakerThread类一样,EaterThread
也会暂停一段随机长的时间。这段暂停时间模拟的是
“吃蛋糕花费的时间”。
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){
}
}
}
Table
类用于表示放置蛋糕的桌子。可放置的蛋糕个数通过构造函数来指定。在示例程序中,蛋糕以
String
实例来表示。
Table
类声明了一个
String
数组类型的
buffer
字段,用于作为蛋糕的实际放置位置。为了正确放置
(put)
和取
(take)
蛋糕,
Table
类还声明了
int
类型的字段
tail
head
count
。各字段的含义分别如下所示。
(1) tail
字段:表示下一次放置
(put)
蛋糕的位置
(2) Head
字段:表示下一次取
(take)
蛋糕的位置
(3) 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.tail = 0;
this.head =0;
this.count = 0;
}
//
放置蛋糕
public synchronized void put(String cake) throw 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() throw 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;
}
}
该程序的运行结果如下图所示:
你会发现EaterThread
是按
MakerThread
放置蛋糕的顺序取蛋糕的。关于线程运行更详细的内容,后面篇章再续
......
更多技术资讯可关注:gzitcast