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类会创建一个桌子上的实例,并启动表示糕点师和客人的线程。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类用于制作蛋糕,并将其放到桌子上,也就是糕点师,为了简单起见,我们像下面这样以“流水号”和制作该蛋糕的“线程名称”来表示蛋糕。 为了使程序的运行结果方便查看,蛋糕的流水号在所有的糕点师之间是共用的。为此,这里将流水号(id)声明为静态字段。 MakerThread会先暂停一段随机长(0到1000毫秒之间)的时间,然后再调用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类型的字段tail,head和count。各字段的含义分别如下所示。 (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放置蛋糕的顺序取蛋糕的。关于线程运行更详细的内容,后面篇章再续......
|