A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

Producer-Consumer线程模式的简单介绍
Producer是“生产者”的意思,指的是生成数据的线程,Consumer则是“消费者”的意思,指的是使用数据的线程。
生产者安全地将数据交给消费者。虽然仅是这样看似简单的操作,但当生产者和消费者以不同的线程运行时,两者之间的处理速度差异便会引起问题。例如,消费者想要获取数据,可数据还没生成,或者生产者想要交付数据,而消费者的状态还无法接收数据等。
Producer-Consumer模式在生产者和消费者之间加入了一个“桥梁角色”,该桥梁角色
用于消除线程间处理速度的差异。
一般来说,在该模式中,生产者和消费者都有多个,当然生产者和消费者有时也会只有一个。当两者都只有一个时,我们称之为Pipe模式。
为了理解Producer-Consumer模式,我们先来看一个示例程序,在这个示例程序中,有3位糕点师制作蛋糕并将其放到桌子上,然后有3位客人来吃这些蛋糕。程序如下。
先理清需求:
(1)糕点师(MakerThread)制作蛋糕(String),并将其放置到桌子(Table)
(2)桌子上最多可放置3个蛋糕
(3)如果桌子上已经放满3个蛋糕时糕点师还要再放置蛋糕,必须等桌子上空出位置
(4)客人(EaterThread)取桌子上的蛋糕吃
(5)客人按蛋糕被放置到桌子上的顺序来取蛋糕
(6)当桌子上1个蛋糕都没有时,客人若要取蛋糕,必须等到桌子上新放置了蛋糕
Main类会创建一个桌子上的实例,并启动表示糕点师和客人的线程。MakerThreadEaterThread的构造函数中传入的数字只是用来作为随机数的种子,数值本身并没有什么特别的意思。
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类用于制作蛋糕,并将其放到桌子上,也就是糕点师,为了简单起见,我们像下面这样以“流水号”和制作该蛋糕的“线程名称”来表示蛋糕。
为了使程序的运行结果方便查看,蛋糕的流水号在所有的糕点师之间是共用的。为此,这里将流水号(id)声明为静态字段。
MakerThread会先暂停一段随机长(01000毫秒之间)的时间,然后再调用Table类的Put方法将制作好的蛋糕放置到桌子上。暂停的这段时间模拟的是“制作蛋糕所花费的时间”。
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){                       
                }       
        }
        private static synchronized int nextId(){
                return id++;
        }
}
EaterThread类用于表示从桌子上取蛋糕吃的客人(虽说是“吃”,其实这里只是执行sleep操作)。客人通过Table类的take方法取桌子上的蛋糕。然后,与MakerThread类一样,EaterThread也会暂停一段随机长的时间。这段暂停时间模拟的是“吃蛋糕花费的时间”。
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类用于表示放置蛋糕的桌子。可放置的蛋糕个数通过构造函数来指定。在示例程序中,蛋糕以String实例来表示。Table类声明了一个String数组类型的buffer字段,用于作为蛋糕的实际放置位置。为了正确放置(put)和取(take)蛋糕,Table类还声明了int类型的字段tailheadcount。各字段的含义分别如下所示。
(1) tail字段:表示下一次放置(put)蛋糕的位置
(2) Head字段:表示下一次取(take)蛋糕的位置
(3) Count字段:表示当前桌子上放置的蛋糕个数
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.tail = 0;
                this.head =0;
                this.count = 0;
        }
        //放置蛋糕
        public synchronized void put(String cake) throw 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() throw 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;
        }
}
该程序的运行结果如下图所示:
你会发现EaterThread是按MakerThread放置蛋糕的顺序取蛋糕的。关于线程运行更详细的内容,后面篇章再续......


您需要登录后才可以回帖 登录 | 加入黑马