第五章 生产者消费者模式

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异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值