生产者安全地将数据交给消费者。
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
private char[] buffer = null;
private char index = 0;
private final Random random;
public ProducerThread(Exchanger
this.exchanger = exchanger;
this.buffer = buffer;
this.random = new Random(seed);
}
public void run(){
try {
while(true){
for(int i=0;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
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
private char[] buffer = null;
private final Random random;
public ConsumerThread(Exchanger
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[] buffer1 = new char[10];
char[] buffer2 = new char[10];
new ProducerThread(exchanger, buffer1, 300000).start();;
new ConsumerThread(exchanger, buffer2, 200000).start();;
}
}
Exchanger交换缓冲区图
由于将有多个线程使用Channel角色,所有我们需要在Channel角色中执行互斥处理。在Channel角色中,从Producer角色获取Data角色的部分和向Consumer传递Data的部分,都使用了Guarded Suspension(满足条件的获取或传递),这样,线程之间可以安全地进行通信。如果Channel角色可存储的Data角色数量足够多,那么便可以缓解Producer角色和Consumer角色之间处理速度的差异
这就是Producer-Consumer模式