这一章我们介绍Producer-Consumer模式。
示例程序的场景如下:
- MakerThread制作蛋糕(String),并放置在桌子(Table)上;
- 桌子上的蛋糕数量超过最大限额,MakerThread需进行等到;
- EaterThread消费桌子(Table)的蛋糕;
- 桌子上蛋糕数量为0时,EaterThread需进行等待;
- MakerThread和EaterThread均有多个。
话不多说,上链接(哦不,上代码^^)
import java.util.Random
public class MakerThread extends Thread{
private final Random random;
private final Table table;
private static int id = 0;
public MakerThread(String name, long seed, Table table){
super(name);
this.random = new Random(seed);
this.table = table;
}
@Override
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++;
}
}
import java.util.Random;
public class EaterThread extends Thread{
private final Random random;
private Table table;
public EaterThread(String name, long seed, Table table){
super(name);
this.random = new Random(seed);
this.table = table;
}
@Override
public void run(){
try{
while(true){
String cake = table.take();
Thread.sleep(random.nextInt(1000));
}
}catch(InterruptedException e){
}
}
}
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{
while(count >= buffer.length){
wait();
}
System.out.println(Thread.currentThread().getName() + " puts" + cake);
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 class Main{
public static void main(String[] args){
Table table = new Table(3);
new MakerThread("MakerThread-1", 31415, table).start();
new MakerThread("MakerThread-2", 65831, table).start();
new MakerThread("MakerThread-3", 97312, table).start();
new EaterThread("EaterThread-1", 43813, table).start();
new EaterThread("EaterThread-2", 63813, table).start();
new EaterThread("EaterThread-3", 89323, table).start();
}
}
Producer-Consumer模式中包含了Data、Producer、Consumer、Channel四种角色。Data由Producer产生,供Consumer消费,示例代码中为String类型的Cake蛋糕;Producer生产Data,并传递给Channel,由示例代码中的MakerThread扮演;Consumer从Channel中消费Data,由示例代码中的EaterThread扮演;Channel从Producer中获取Data,并接收Consumer的请求。由于可能存在多个Consumer或者Producer进程,Channel需要进行互斥处理(声明synchronized关键字)。当然,如果是单个Consumer或者Producer进程,则对应的函数无需声明为synchronized。类图如下所示:
需要注意的是,我们在Table类中put方法和get方法都添加了中断异常(InterruptedException),典型添加中断异常的方法包括:wait方法、sleep方法和join方法。上诉三个方法均需要继续等待,wait方法等待notify/notifyAll;sleep方法等待时间片耗完;join方法等待其他线程终止。如果想在等待时间内提前终止(取消等待),那么interrupt方法便派上用场。
- sleep方法和interrupt方法
线程A执行了sleep方法:Thread.sleep(600000000);,如果想取消A的暂停状态,需要在其他线程中执行取消操作(因为A目前正处于暂停状态)。假设线程B执行下述语句:a.interrupt();变量a保存着线程A对应的Thread实例。interrupt方法是Thread的实例方法,任何线程均可以调用其他线程的interrupt方法,执行interrupt方法前不需要获取待取消线程的锁。interrupt方法被调用后,正在sleep的线程回终止暂停状态,并抛出interruptedException异常。
- wait方法和interrupt方法
对正在wait的线程执行interrupt方法,意即告诉该线程无需等待notify/notifyAll方法的调用,直接从等待队列中出来。假设A线程处于等待队列,B线程执行:a.interrupt();A也会抛出interruptedException异常。但和sleep、join方法不同的是,A线程需要先重新获得锁之后才会抛出interruptedException异常。
- join方法和interrupt方法
类似sleep方法,执行interrupt方法之后会立即到catch的语句中。
这里需要注意的是,执行线程的interrupt方法不一定会抛出interruptedException,这是因为wait、sleep、join方法内部检查了线程的中断状态进而抛出interruptedException,而执行类似1+5; a=100;之类的操作就不会抛出异常而继续执行。如下所示:
好啦,关于Producer-Consumer模式我们就介绍这么多,下一章介绍Read-Write Lock模式,欢迎大家继续阅读~