3.2生产者和消费者(Producers and Consumers)

来源:互联网 发布:主人网络怎么开启 编辑:程序博客网 时间:2024/06/06 14:21

一个古典的例子关于一个生产者线程和一个消费者线程之间的通信。生产者线程产生数据条目供消费者线程消费。每一个生产数据条目都存储在一个共享的变量。

想像一下,线程运行在存在不同的速度。消费者接受先前进程的数据条目之前,生产者可能生产一个新的数据条目和记录在共享的变量中。也可以是,一个新的数据条目产生之前,消费者可能接受共享变量的内容。

为了去解决这个问题,生产者线程必须等待直到先前产生的数据条目已经被消费,它才可以被通知唤醒;和一个消费者线程必须等待直到一个新的数据条目被生产,它才可以被通知唤醒。Listing3-1展示了如何去完成这个任务通过wait()和notify()方法。

package com.owen.thread.chapter3;public class PC{public static void main(String[] args){Shared s = new Shared();new Producer(s).start();new Consumer(s).start();}}class Shared{private char c;private volatile boolean writeable = true;synchronized void setSharedChar(char c){while (!writeable)try{wait();} catch (InterruptedException ie){}this.c = c;writeable = false;notify();}synchronized char getSharedChar(){while (writeable)try{wait();} catch (InterruptedException ie){}writeable = true;notify();return c;}}class Producer extends Thread{private final Shared s;Producer(Shared s){this.s = s;}@Overridepublic void run(){for (char ch = 'A'; ch <= 'Z'; ch++){s.setSharedChar(ch);System.out.println(ch + " produced by producer.");}}}class Consumer extends Thread{private final Shared s;Consumer(Shared s){this.s = s;}@Overridepublic void run(){char ch;do{ch = s.getSharedChar();System.out.println(ch + " consumed by consumer.");} while (ch != 'Z');}}

这个应用创建一个Share对象和两个线程,两个线程获取一个对象引用的副本。这个生产者请求对象setShareChar()方法去保存26个字母其中一个;消费者请求对象getShareChar()方法去获取每一个字母。

writeable实例域有两种状态:生产者等待消费者去消费数据,和消费者等待生产者去产生新的数据。这个使生产者和消费者之间有序地进行。下面的方案,这里首先消费者先执行,说明这个有序性:

        (1)这个消费者执行s.getShareChar()去接收一个字母。

       (2) 在同步方法内部,消费者请求wait()方法,因为writeable保持true。消费者一直在等待直到它从生产者那里得到通知。

        (3)生产者最终执行s.setShareChar(ch);

        (4)当生产者进入到同步方法(这是可能的,因为消费者释放锁在wait()内部方法优先于等待),生产者重新发现writeable的值是true和不能请求wait().

       (5) 生产者保存字母,让writeable为false(这个将导致生产在下一次setShareChar()请求时处于等待,如果消费者不能消费这个字母,那么将会一直处于等待)和请求notify()去唤醒消费者(假设消费者在等待)。

        (6)生产者存在setShareChar(char c).

       (7) 消费者唤醒(和再次请求锁),让writeable为true(这会导致消费者在下一次getShareChar()请求时处于等待,当生产者不能生产字母,那它将一直处于等待),通知生产者去唤醒这个线程(假设生产者在等待),和返回一个shared。

执行上面的代码,你可能会得到如下的结果:

W produced by producer.W consumed by consumer.X produced by producer.Y produced by producer.X consumed by consumer.Y consumed by consumer.Z produced by producer.Z consumed by consumer.

尽管同步工作是正确的,你可能在多消费的信息之前你会发现多个输出信息。

A produced by producer.B produced by producer.A consumed by consumer.B consumed by consumer.

你也可能在生产的信息之前发现消费信息输出。

V produced by producer.V consumed by consumer.
这些信息的输出不是意味着生产者和消费者的线程不是同步。相反,请求setShareChar()方法返回的结果是通过应用的System.out.println()方法请求的,而这个方法是不同步的,和请求getShareChar()方法是通过应用的System.out.prinltn()方法请求的,而这个方法是不同步的。通过擦除每个这样的方法,加上同步锁在Share对象引用的s,那么输出的顺序就会是正确的,如Listing3-2的例子所示:

package com.owen.thread.chapter3;public class PC3_2{public static void main(String[] args){Shared3_2 s = new Shared3_2();new Producer3_2(s).start();new Consumer3_2(s).start();}}class Shared3_2{private char c;private volatile boolean writeable = true;synchronized void setSharedChar(char c){while (!writeable)try{wait();} catch (InterruptedException ie){}this.c = c;writeable = false;notify();}synchronized char getSharedChar(){while (writeable)try{wait();} catch (InterruptedException ie){}writeable = true;notify();return c;}}class Producer3_2 extends Thread{private final Shared3_2 s;Producer3_2(Shared3_2 s){this.s = s;}@Overridepublic void run(){for (char ch = 'A'; ch <= 'Z'; ch++){synchronized (s){s.setSharedChar(ch);System.out.println(ch + " produced by producer.");}}}}class Consumer3_2 extends Thread{private final Shared3_2 s;Consumer3_2(Shared3_2 s){this.s = s;}@Overridepublic void run(){char ch;do{synchronized (s){ch = s.getSharedChar();System.out.println(ch + " consumed by consumer.");}} while (ch != 'Z');}

执行例子输出信息如下:

A produced by producer.A consumed by consumer.B produced by producer.B consumed by consumer.C produced by producer.C consumed by consumer.D produced by producer.D consumed by consumer.
 源码下载:Git@github.com:owenwilliam/Thread.git