Producer 生产者,产生数据的线程
Consumer 消费者,使用数据的线程
【示例程序】
桌子上最多可放置3个蛋糕
如果桌子上已经放满3个蛋糕时糕点师还要再放置蛋糕,必须等到桌子上空出位置。
客人(EaterThread)取桌子上的蛋糕吃
客人按蛋糕被放置到桌子上的顺序来取蛋糕
当桌子上1个蛋糕都没有时,客人若取蛋糕,必须等到桌子上新放置了蛋糕
名字 | 说明 |
Main | 测试程序行为的类 |
MakerThread | 表示糕点师的类 |
EaterThread | 表示客人的类 |
Table | 表示桌子的类 |
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类
用于制作蛋糕并将其放置到桌子上。无线循环执行“制作蛋糕,放置在桌子上”。
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) {}
}
public 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){}
}
}
Table类
用于表示放置蛋糕的桌子。可防止的蛋糕个数通过构造函数来指定。
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;
}
}
【Producer-Consumer模式中登场的角色】
1.Data
Data角色由Producer角色生成,供Consumer使用。
2.Producer生产者
Producer角色生成Data角色,并将其传递给Channel角色。
3.Consumer角色
Consumer角色从Channel角色获取Data角色并使用。
4.Channel通道
Channel角色保管从Producer角色获取的Data角色,还会响应Consumer角色的请求,传递Data角色。
【拓展思路的要点】守护安全性的Channel角色(可复用性)
在Producer-Consumer模式中,承担安全守护责任的是Channel角色。Channel角色执行线程间的互斥处理,确保生产者角色正确地将Data角色传递给Consumer角色。
【延伸阅读】理解InterruptedException异常
加了throws InterruptedException的方法可能会发费时间,但可以取消。
1.花费时间的方法
线程执行wait方法,会进入等待队列,等待被notify/notifyAll。在等待期间,线程是不运行的,但需要花费时间来等待被notify/notifyAll。
线程执行sleep方法后,会暂停执行(暂停多长时间由参数指定)。
线程执行join方法后,会等待指定线程终止。该方法需要花费时间,来等待指定线程终止。
2.sleep和interru方法
假设线程Alice执行下面这条语句,使用sleep方法暂停了运行。
Thread.sleep(xxx);
由于线程Alice正处于暂停状态,所以只能由其他线程来执行取消操作。
alice.interrupted();
变量alice里保存着与线程Alice对应的Thread实例。
这里使用的interrrupt方法是Thread类的实例方法。当执行interrupt时,线程并不需要获取Thread实例的锁。任何线程任何时候都可以调用其他线程的interrupt方法。
interrupt方法被调用后,正在sleep的线程会终止暂停状态,抛出InterruptedException异常。此处抛出异常的线程是Alice。
这样线程Alice的控制器就会转移到捕捉该异常的catch语句块中。
3.wait个interrupt方法
线程Alice使用wait进行等待时,与使用sleep暂停运行时一样,也是可以取消的。当使用interrupt方法时,该操作意即告诉正在wait的线程不用再等待notify/notifyAll了,从等待队列里出来吧。
在wait的情况下,需要注意锁的问题。线程在进入等待队列时,已经释放了锁。当正在wait的线程被调用interrupt方法时,该线程会在重新获取锁之后抛出InterruptedException异常。在获取锁之前,线程不会抛出InterruptedException异常。