在有些时候,我们需要在几个或多个线程中按照一定的秩序来共享一定的资源。例如生产者--消费者的关系,在这一对关系中实际情况总是先有生产者生产了产品后,消费者才有可能消费;又如在父--子关系中,总是先有父亲,然后才能有儿子。然而在没有引入等待通知机制前,我们得到的情况却常常是错误的。这里我引入《用线程获得强大的功能》一文中的生产者--消费者的例子:
/* ==================================================================================
* 文件:ThreadDemo07.java
* 描述:生产者--消费者
* 注:其中的一些注释是我根据自己的理解加注的
* ==================================================================================
*/
// 共享的数据对象
class ShareData{
private char c;
public void setShareChar(char c){
this.c = c;
}
public char getShareChar(){
return this.c;
}
}
// 生产者线程
class Producer extends Thread{
private ShareData s;
Producer(ShareData s){
this.s = s;
}
public void run(){
for (char ch = 'A'; ch <= 'Z'; ch++){
try{
Thread.sleep((int)Math.random() * 4000);
}catch(InterruptedException e){}
// 生产
s.setShareChar(ch);
System.out.println(ch + " producer by producer.");
}
}
}
// 消费者线程
class Consumer extends Thread{
private ShareData s;
Consumer(ShareData s){
this.s = s;
}
public void run(){
char ch;
do{
try{
Thread.sleep((int)Math.random() * 4000);
}catch(InterruptedException e){}
// 消费
ch = s.getShareChar();
System.out.println(ch + " consumer by consumer.");
}while(ch != 'Z');
}
}
class Test{
public static void main(String argv[]){
ShareData s = new ShareData();
new Consumer(s).start();
new Producer(s).start();
}
}
在以上的程序中,模拟了生产者和消费者的关系,生产者在一个循环中不断生产了从A-Z的共享数据,而消费者则不断地消费生产者生产的A-Z的共享数据。我们开始已经说过,在这一对关系中,必须先有生产者生产,才能有消费者消费。但如果运行我们上面这个程序,结果却出现了在生产者没有生产之前,消费都就已经开始消费了或者是生产者生产了却未能被消费者消费这种反常现象。为了解决这一问题,引入了等待通知(wait/notify)机制如下:
1、在生产者没有生产之前,通知消费者等待;在生产者生产之后,马上通知消费者消费。
2、在消费者消费了之后,通知生产者已经消费完,需要生产。
下面修改以上的例子(源自《用线程获得强大的功能》一文):
/* ==================================================================================
* 文件:ThreadDemo08.java
* 描述:生产者--消费者
* 注:其中的一些注释是我根据自己的理解加注的
* ==================================================================================
*/
class ShareData{
private char c;
// 通知变量
private boolean writeable = true;
// -------------------------------------------------------------------------
// 需要注意的是:在调用wait()方法时,需要把它放到一个同步段里,否则将会出现
// "java.lang.IllegalMonitorStateException: current thread not owner"的异常。
// -------------------------------------------------------------------------
public synchronized void setShareChar(char c){
if (!writeable){
try{
// 未消费等待
wait();
}catch(InterruptedException e){}
}
this.c = c;
// 标记已经生产
writeable = false;
// 通知消费者已经生产,可以消费
notify();
}
public synchronized char getShareChar(){
if (writeable){
try{
// 未生产等待
wait();
}catch(InterruptedException e){}
}
// 标记已经消费
writeable = true;
// 通知需要生产
notify();
return this.c;
}
}
// 生产者线程
class Producer extends Thread{
private ShareData s;
Producer(ShareData s){
this.s = s;
}
public void run(){
for (char ch = 'A'; ch <= 'Z'; ch++){
try{
Thread.sleep((int)Math.random() * 400);
}catch(InterruptedException e){}
s.setShareChar(ch);
System.out.println(ch + " producer by producer.");
}
}
}
// 消费者线程
class Consumer extends Thread{
private ShareData s;
Consumer(ShareData s){
this.s = s;
}
public void run(){
char ch;
do{
try{
Thread.sleep((int)Math.random() * 400);
}catch(InterruptedException e){}
ch = s.getShareChar();
System.out.println(ch + " consumer by consumer.**");
}while (ch != 'Z');
}
}
class Test{
public static void main(String argv[]){
ShareData s = new ShareData();
new Consumer(s).start();
new Producer(s).start();
}
}
在以上程序中,设置了一个通知变量,每次在生产者生产和消费者消费之前,都测试通知变量,检查是否可以生产或消费。最开始设置通知变量为true,表示还未生产,在这时候,消费者需要消费,于时修改了通知变量,调用notify()发出通知。这时由于生产者得到通知,生产出第一个产品,修改通知变量,向消费者发出通知。这时如果生产者想要继续生产,但因为检测到通知变量为false,得知消费者还没有生产,所以调用wait()进入等待状态。因此,最后的结果,是生产者每生产一个,就通知消费者消费一个;消费者每消费一个,就通知生产者生产一个,所以不会出现未生产就消费或生产过剩的情况。