生产者与消费者模式是一种常见的模式,出现在许多资源管理中。
在多线程中,生产者(MyProducer)与消费者(MyConsummer)之间共享一个产品管道(MyChannel),生产者make()的产品(Product)放入管道中,消费者consumer产品时从管道中取产品,他们之间的协同通过市场(MyMarket)管理。它们之间关系如下面用例图所示。代码附后。
生产者:不知道消费者是谁,更不知道消费者如何消费。它只与Channel打交道。
public abstract class MyProducer implements Runnable{
protected final MyChannel myChannel;
protected MyProduct[] products;
public MyProducer(MyChannel myChannel1){
this.myChannel=myChannel1;
}
@Override
public abstract void run();
protected abstract void make();
}
一个具体生产者(线程)
public class ASimpleProducer extends MyProducer {
public ASimpleProducer(MyChannel myChannel1) {
super(myChannel1);
}
@Override
public void run() {// This only task is to put products in the channel
while (true) {
this.make();
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
Logger.getLogger(ASimpleProducer.class.getName()).log(Level.SEVERE, null, ex);
}
for (MyProduct product : products) {
myChannel.put(product);
}
}
}
@Override
protected void make() {
products = new ASimpleProduct[1];
products[0] = new ASimpleProduct(Thread.class.getName());
}
}
一个非线程的生产者
public class MyNoThreadProducer{
private final SizeUnlimitedChannel pChannel;
public MyNoThreadProducer(SizeUnlimitedChannel pChannel1){
pChannel=pChannel1;
}
public void produce() {
for (int i = 0;; i++) {
ASimpleProduct product = new ASimpleProduct("" + i);
pChannel.put(product);
try {
Thread.sleep(i);
} catch (InterruptedException ex) {
Logger.getLogger(MyMarketSizeUnlimited.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
消费者:不知道生产者是谁,更不要说生产者如何生产了。它只与Channel打交道
public abstract class MyConsumer extends Thread{
protected final MyChannel myChannel;
public MyConsumer(MyChannel myChannel1){
this.myChannel=myChannel1;
}
@Override
public abstract void run();
protected abstract void consum(MyProduct product);
}
具体消费者
public class ASimpleConsumer extends MyConsumer {
public ASimpleConsumer(MyChannel myChannel1) {
super(myChannel1);
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(50);
consum(myChannel.take());
}
} catch (InterruptedException ex) {
Logger.getLogger(ASimpleConsumer.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Override
protected void consum(MyProduct product) {
}
}
产品
public interface MyProduct {
}
具体产品
public class ASimpleProduct implements MyProduct{
String name;
public ASimpleProduct(String myName){
this.name=myName;
}
@Override
public String toString(){
return name;
}
}
生产者与消费者通信的管道池
抽象管道池
public interface MyChannel {
public void put(MyProduct product);
public MyProduct take();
}
有容量限制的管道池
public class SizeLimitedChannel implements MyChannel {
private final MyProduct[] queue;
private int tail, head, count;
public SizeLimitedChannel(int size1) {
queue = new MyProduct[size1];
this.head = 0;
this.tail = 0;
this.count = 0;
}
@Override
public synchronized void put(MyProduct e) {
try {
while (count >= queue.length) {
wait();
//System.out.println(Thread.currentThread().getName()+"put() is waiting");
}
queue[count] = e;
tail = (tail + 1) % queue.length;
count++;
//System.out.println(Thread.currentThread().getName()+"Put in one.Now the number of the products in the channel is:"+count);
notifyAll();
} catch (InterruptedException exception) {
}
}
@Override
public synchronized MyProduct take() {
MyProduct e =null;
try {
while (count <= 0) {
wait();
//System.out.println(Thread.currentThread().getName()+"take() is waiting!");
}
e = queue[head];
count--;
//System.out.println(Thread.currentThread().getName()+"Take one out.Now the number of the products in the channel is:"+count);
notifyAll();
} catch (InterruptedException exception) {
}
return e;
}
}
无容量限制的管道池
public class SizeUnlimitedChannel implements MyChannel {
private final LinkedList<MyProduct> queue = new LinkedList();
private int count = 0;
@Override
public synchronized void put(MyProduct e) {
queue.addLast(e);
this.recount(1);
System.out.println("Put in one. count is:" + this.count);
notifyAll();
}
@Override
public synchronized MyProduct take() {
MyProduct e = null;
try {
while (queue.size() <= 0) {
wait();
}
} catch (InterruptedException ex) {
Logger.getLogger(SizeUnlimitedChannel.class.getName()).log(Level.SEVERE, null, ex);
}
e = queue.removeFirst();
recount(-1);
// System.out.println("Take one away. the count is:" + this.count);
if(this.count==0){
System.out.println("The channel now is empty!");
}
return e;
}
public synchronized void recount(int i) {
this.count = this.count + i;
}
}
市场1:
Channel有容量限制。
生产者与消费者都是线程,都需要等待MyChannel的许可:对生产者来说,channel中产品的数量不大于容量;对消费者来说,channel中产品的数量不小于0。
public class MyMarketSizeLimited {
public static void main(String[] args) {
SizeLimitedChannel channel=new SizeLimitedChannel(5);
///////////////Producer//////////////////////
new Thread(new ASimpleProducer(channel)).start();
new Thread(new ASimpleProducer(channel)).start();
///////////////Consumer//////////////////
new Thread(new ASimpleConsumer(channel)).start();
new Thread(new ASimpleConsumer(channel)).start();
}
}
市场2:
贪婪生产者:Channel无容量限制。
生产者不是线程,消费者是线程,产品池采用无容量限制的池。因此,只有消费者需要等待MyChannel的许可:channel中产品的数量不小于0。
public class MyMarketSizeUnlimited {
public static void main(String[] args) {
SizeUnlimitedChannel pChannel1 = new SizeUnlimitedChannel();
MyNoThreadProducer pt = new MyNoThreadProducer(pChannel1);
new Thread(new ASimpleConsumer(pChannel1)).start();
pt.produce();
}
}
- 角色及职责
在这个模式中,共有5个角色:生产者(Producer)、消费者(Consumer)、产品通道(Channel)、产品(Product)、市场(Market)。其中,生产者和消费者是线程(Thread或Runnable,第2个例子中生产者不是线程)。Channel负责这两个线程的同步问题,同步的资源是存放Product的容器(List,Collection等)。 - 分工
尽量让生产者与消费者线程实现可同步的,且不产生资源冲突的任务。
当不可避免地出现资源冲突情况时,让Channel或Market或Product解决,而不是在两个线程(生产者和消费者)中解决,也不是在Market中解决(Market处理较顶层的内容为妥),更不是在Product中解决,唯Channel可以解决。
为什么?
(这个理由似乎不太妥)线程的目的是并行执行,而对于冲突的资源,需要用synchronized来保护,这是串行执行,因此,线程与synchronized的效果是相矛盾的,即在线程中不用synchronized。例如,当消费者负责向数据库的表(例如表A)中写入记录的任务时,如果某字段的值(如A.currentId)需要通过查询语句(从表B.id)获得,而被查询的表的对应的记录(表B.id)随时会改变(例如其他线程修改了其值,不再满足current条件了),这样就产生了前后不一致的情形。
如果这个问题放在Channel中利用synchronized方法解决,由于其是单线程,比消费者可以更早地对产品相关属性进行处理。
(这个理由较好)线程会有多个对象同时运行,而Channel则是唯一的(假设厨师和食客之间只摆一张桌子,则桌子就是Channel.注意是一张,不是二张,如果是两张桌子,则把两张桌子写到一个Channel中。) 对Channel来说,用synchronized限定的资源,由于它只有一个对象(唯一性),所以当有其它类的对象来访问synchronized的对象时,需要先获得锁。(如果有一个Channel类在程序中有两个对象,那么synchronized就没用了!想想两张桌子,对一张桌子的put进行了synchronized,对另一张桌子是无效的!)
为什么synchronized的资源不放在除了Channel外的其他类中,因为其他类都可以有多个对象。各个对象会允许访问者使用自己的资源,另一个对象没有理由和权利去干涉。
再想想,如果一个synchronized资源是通过private方法实现锁定,是否有意义?没有。因为private阻止外边的对象调用,只有自己在调用。如果只有一个对象,则一定不会冲突,如果有多个对象间接调用,则不能阻止冲突。
再进一步,如果一个应用中,涉及到资源冲突,那么不能直接使别人写的现成的Channel,而必须自己写一个,除非别人已经把你要synchronized的内容给写好了。(应该惊讶一下:自己写Channel是必须的!没有自己的Channel,就无法synchronized!) - 编写各个角色的顺序
建议的顺序是先写producer,consumer,再写product,channel和market。先写producer和consumer,使这两个线程的功能尽量单一,避免不必要的混乱和错误。 - 关于线程数目
生产者与消费者都是线程,为了有效利用CPU、内存、网络等资源,需要合理地设置线程数目。如果是计算线程,则线程数目要考虑CPU的核数,如果是持久化线程,则考虑数据库的连接池数目。 - 5.