java多线程设计模式之Producer-Consumer模式(一)

来源:互联网 发布:手机网游破解软件 编辑:程序博客网 时间:2024/05/17 03:19

     Producer是生产者的意思,指的是生成数据的线程,Consumer则是消费者的意思,指的是使用数据的线程,这里我们要实现的是如何安全的将数据从生产者交给消费者,看似简单但是当两者处于不同的线程时,处理速度差异便会产生问题。对于这个问题,我们可以在两者之间加入一个桥梁通道的角色,具体的设计如下:


情景创建,假如说有个蛋糕店,糕点师制作蛋糕,顾客吃蛋糕,糕点师制作完成后将蛋糕放在桌子上,桌子上做多可以放置三个蛋糕,如果桌子上满了就必须等到腾出位置,客人如果吃没了就要等待蛋糕重新放置到桌子上。

首先构建用于存放蛋糕的Table类:

public class Table {private final String[] buffer;private int tail;private int head;private int count;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);//守护条件,当条件成立时,执行wait()while(count>=buffer.length){wait();}buffer[tail]=cake;tail=(tail+1)%buffer.length;count++;notifyAll();}//拿取蛋糕public synchronized String take()throws InterruptedException{//守护条件,当条件成立时,执行wait()while(count<=0){wait();}String cake = buffer[head];head = (head+1)%buffer.length;count--;//notifyAll()方法调用之后会将原来wait()方法等待的线程唤醒//继续执行noifyAll()方法后面的语句,执行notifyAll方法时必须获取对应的实例的锁notifyAll();System.out.println(Thread.currentThread().getName()+" takes "+cake);return cake;}}
     其中对cake操作的方法:get和put都是用synchronized修饰,保证线程的安全性,同时通过构造函数来传递所要构建的Table 的容量。其中也是用到了之前将讲到的GuardSuspension模式,设置while语句对队列中的内容的判断作为守护条件。

用于表示蛋糕制作的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() + "]";table2.put(cake);}}catch(InterruptedException e){}}//保证修改公用字段id在多个线程修改的时候能够保持互斥性private static synchronized int nextId(){return id++;}}
   此类适用于向Table中添加蛋糕的,同时,再run()方法中设置sleep方法来模拟写入的耗时过程;类的初始化需要传入一个共享的Table类的实例,所以Table类要设计为线程安全的类才可以。这样就实现了往Table中添加cake的过程。

     下面模拟客人拿取蛋糕的过程,构建了一个EaterThread类:

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;this.random = new Random(seed);}public void run(){try{while(true){String cake = table2.take();Thread.sleep(random.nextInt(1000));}}catch(InterruptedException e){}}}

   这个类模拟了对蛋糕的获取的行为,同样的类的初始化需要传入一个共享的Table类的实例,这就看出了Table类的安全性对整个的模拟过程的重要性,Table类起到了一个桥梁的角色,而且含包含了对数据的操作的方法,所以重点则是对于Table类的设计。

     下面编写一个main函数来测试一下:

public class ProducerConsumerMain {public static void main(String[] args) {Table2 table2 = new Table2(3);new MakerThread("MakerThread-1",table2,31415).start();new MakerThread("MakerThread-2",table2,92653).start();new MakerThread("MakerThread-3",table2,58979).start();new EaterThread("EaterThread-1",table2,32384).start();new EaterThread("EaterThread-2",table2,62643).start();new EaterThread("EaterThread-3",table2,38327).start();}}
    模拟了3个糕点师和3客人的情况,所以各启动了3个线程,执行结果如下:


     关于Channel角色存在的意义,因为正因为有了Channel角色的存在才使得整个过程能够协调有序的进行,所以在这里我们应该这么考虑:线程的协调运行系要考虑放在中间的东西,同时应该注意应该保护的东西。




阅读全文
0 0
原创粉丝点击