public class Main {
public static void main(String[] args) {
Table table=new Table(3);
new MakerThread("MakerThread-1", table, 12345).start();
new MakerThread("MakerThread-2", table, 54345).start();
new MakerThread("MakerThread-3", table, 62345).start();
new EaterThread("EaterThread-1", table, 23455).start();
new EaterThread("EaterThread-2", table, 43455).start();
new EaterThread("EaterThread-3", table, 53455).start();
}
}
public class EaterThread extends Thread{
private final Table table;
private final Random random;
public EaterThread(String name,Table table,long seed) {
super(name);
this.table=table;
random=new Random(seed);
}
@Override
public void run() {
try {
while (true) {
String cake=table.take();
Thread.sleep(random.nextInt(1000));
}
} catch (Exception e) {
// TODO: handle exception
}
}
}
public class MakerThread extends Thread{
private final Table table;
private final Random random;
private static int id=0;
public MakerThread(String name,Table table,long seed) {
super(name);
this.table=table;
random=new Random(seed);
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(random.nextInt(1000));
String cake="[ Cake No: "+nextId()+" by "+getName()+"]";
table.put(cake);
}
} catch (Exception e) {
// TODO: handle exception
}
}
private static synchronized int nextId() {
return ++id;
}
}
public class Table {
private final String[] buffer;
private int count;
private int tail;
private int head;
public Table(int count) {
buffer=new String[count];
count=0;
tail=0;
head=0;
}
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 synchronized void put(String cake) throws InterruptedException {
System.out.println(Thread.currentThread().getName()+" put "+cake);
while (count>=buffer.length) {
wait();
}
buffer[tail]=cake;
tail=(tail+1)%buffer.length;
count++;
notifyAll();
}
}
运行就过如下:
MakerThread-1 put [ Cake No: 1 by MakerThread-1]
EaterThread-2 takes [ Cake No: 1 by MakerThread-1]
MakerThread-1 put [ Cake No: 2 by MakerThread-1]
EaterThread-1 takes [ Cake No: 2 by MakerThread-1]
MakerThread-1 put [ Cake No: 3 by MakerThread-1]
EaterThread-3 takes [ Cake No: 3 by MakerThread-1]
MakerThread-3 put [ Cake No: 4 by MakerThread-3]
EaterThread-2 takes [ Cake No: 4 by MakerThread-3]
MakerThread-2 put [ Cake No: 5 by MakerThread-2]
EaterThread-2 takes [ Cake No: 5 by MakerThread-2]
MakerThread-3 put [ Cake No: 6 by MakerThread-3]
EaterThread-1 takes [ Cake No: 6 by MakerThread-3]
MakerThread-1 put [ Cake No: 7 by MakerThread-1]
EaterThread-3 takes [ Cake No: 7 by MakerThread-1]
MakerThread-1 put [ Cake No: 8 by MakerThread-1]
MakerThread-1 put [ Cake No: 9 by MakerThread-1]
EaterThread-2 takes [ Cake No: 8 by MakerThread-1]
EaterThread-1 takes [ Cake No: 9 by MakerThread-1]
MakerThread-2 put [ Cake No: 10 by MakerThread-2]
MakerThread-2 put [ Cake No: 11 by MakerThread-2]
Producer-Consumer Pattern 的所有参与者
程序的类图如下
Producer是“生产者”的意思,是指产生数据的线程,而Consumer是“消费者”的意思,意指使用数据的线程。
生产者必须将数据安全的交给消费者。虽然这是这样的问题,但当生产者和消费者在不同线程上运行时,两者的处理速度差将是最大的问题。当消费者要取数据时生产者还没建立数据,或者是生产者建立出数据时消费者的状态还没办法接受数据等等。
Producer-Consumer Pattern是在生产者与消费者之间加入一个“桥梁参与者”,以这个桥梁参与者缓冲线程之间的处理速度差。Channel参与者介于Producer参与者、Consumer参与者之间,担任传递Data参与者中继站,通道的角色。
保护安全性的Channel参与者
范例程序中,Table类的put和take方法都是用了Guarded Suspension Pattern,但MakerThread类与EaterThread类都不想依于Table类的详细实现,也就是说MakerThread不比理会其他线程,只要调用put方法就好,而EaterThread也是只要会调用take方法就可以。使用synchronized、wait、notifyAll这些考略到多线程操作的程序代码,全部隐藏在Channel参与者Table类里面。
中间者存在的隐含的意义
因为Channel参与者的存在,Producer参与者和Consumer参与者这条多线程,才能保持协力、合作。而在在Single Thread Execution Pattern中曾经说过“Synchronized是在保护什么”?当时我们说过,思索线程的共享互斥问题时,把观察切入点放在“保护着什么”上面,会比较容易找到问题的结症。
上面的思维若整理成口诀,可以得到两句话:
一。线程的合作要想“放在中间的东西”
二。线程的互斥要想“应该保护的东西”
InterruptedException异常
Java的标准类链接库中,后面接着throws InterrupedException的方法的代表选手,有下面这3位:
Java.lang.Object类的wait方法
Java.lang.Thread类的sleep方法
Java.lang.Thread类的join方法
这个通常告诉我们下面两件事:
一、这是“需要花点时间”的方法
二、这是“可以取消”的方法
用一句话来说,后面接着throws InterruptedException的方法,是可能会花一点时间,但是可以取消的方法。
一、需要花点时间的方法
执行wait方法的线程,会进入等待区等待被notify/notifyAll,在等待期间,线程不会活动。故需要花费等待被notify/notifyAll的时间。
执行sleep方法的线程,会暂停执行参数内所设置的时间,这也是花费时间的方法。
执行join方法的线程,会等待到指定的线程结束为止。也就是会花费直到指定线程结束之前的这段时间
像上面三个方法,分别要等待“被notify/notifyAll、设置的时间、指定的线程结束”,的确都是“需要花点时间”的方法
二、可以取消的方法
因为需要画时间的操作,会降低程序的响应性,所以我们会希望像下面着阳光可以在中途放弃(取消)执行这个方法
1、取消wait方法等待notify/notifyAll的操作
2、取消sleep方法等待设置长度时间的限制
3、取消join方法等待其他线程结束的操作