﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-~~-文章分类-设计模式</title><link>http://www.blogjava.net/java-blog/category/26886.html</link><description /><language>zh-cn</language><lastBuildDate>Sat, 27 Oct 2007 08:31:42 GMT</lastBuildDate><pubDate>Sat, 27 Oct 2007 08:31:42 GMT</pubDate><ttl>60</ttl><item><title>生产者/消费者模型</title><link>http://www.blogjava.net/java-blog/articles/156355.html</link><dc:creator>一步一步努力向上爬</dc:creator><author>一步一步努力向上爬</author><pubDate>Sat, 27 Oct 2007 08:23:00 GMT</pubDate><guid>http://www.blogjava.net/java-blog/articles/156355.html</guid><wfw:comment>http://www.blogjava.net/java-blog/comments/156355.html</wfw:comment><comments>http://www.blogjava.net/java-blog/articles/156355.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/java-blog/comments/commentRss/156355.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/java-blog/services/trackbacks/156355.html</trackback:ping><description><![CDATA[<span><span>
生产者/消费者模型是最基本的并发协作模型，是所有并发协作的基础。可以这么说，其他的并发协作都是供求关系模型的变种。生产者，消费者之间的供求关系可以简单的
使用管道来构造。让我们看两者之间的行为模式：
*生产/消费模型：消费者如果无消费对象，就会阻塞直到有消费对象到达；一个消费对象仅供一个消费者消费。
*BlockingQueue: 如果队列为空，则读取操作将会阻塞直至队列有新的内容到达；队列中对象一旦被读取，将从队列中移走。
由此可见，阻塞队列天然符合生产/消费模型的供求行为模式。在前面展示condition的用法的时候，曾经
用过生产者/消费者模型来举例。那个例子如果改用BlockingQueue来写的话就十分简单
<pre>    ...<br />
BlockingQueue&lt;String&gt; q =new ArrayBlockingQueue&lt;String&gt; (10);<br />
...<br />
public void supply () {<br />
q.put("product by "+Thread.currentThread().getId()+":"+(++productNo));<br />
}<br />
...<br />
public void cunsume () {<br />
String product  =q.take();<br />
System.out.println("consume product:"+product);<br />
}<br />
</pre>
从BlockingQueue也可以看出，它和UNIX系统下面的Pipe十分相似。所不同的不过是两点，首先，pipe是进程间的，命名管道甚至可以在非亲缘进程间使用，而BlockingQueue
目前只是线程间的通信手段。当然，由于java本身强大的动态类装载功能，这个缺陷对java程序之间的沟通限制并不大。其次，pipe是基于字节流的，而BlockingQueue是
基于对象的，这使得BlockingQueue更加易用，不过却让BlockingQueue绑定了Java语言，使进一步成为轻量级本地进程通信工具的难度增大。
<p>从前面对生产/消费模型的行为方式可以看出，生产/消费模型着重于规范消费者的行为模式，当消费速度超过生产速度的时候，消费者就会被阻塞。而对于生产者的行为则没有
规定。当生产速度超过消费速度，生产者的行为模式可以分为以下几种：
#当积压的产品达到一定数量时，生产者被阻塞
#无论有多少积压产品，生产者都不会被阻塞
#不能有任何积压产品，生产者在当前产品未被消费之前，会被阻塞
对于产品来说，也有不同的行为模式
#产品只有在被生产出来一段时间之后才能被消费(先花点时间晾晾干？)
#不同类别的产品被消费的优先级不同(有钻石的话，黄金就先放一边吧:))</p>
<p>根据生产者行为模式的不同Concurrent包提供了不同的BlockingQueue的实现
||Queue种类||行为描述
|ArrayBlockingQueue<e>|有限制的blocking queue，积压的产品不得超过制订数量
|DelayQueue<e>|产品只有生产出一段时间之后，才能被消费，无限制的积压产品
|LinkedBlockingQueue<e>|同时支持有限制的blocking queue，也能支持无限制的积压产品（数量不能超过Integer.MAX_VALUE)
|PriorityBlockingQueue<e>|不同产品的被消费优先级不同，无限制的积压产品
|SynchronousQueue<e>|不允许积压产品</e></e></e></e></e></p>
<p>这些不同的行为模式中，较为常见的除了ArrayBlockingQueue和LinkedBlockingQueue之外，PriorityBlockingQueue也非常重要。举例来说，如果我们利用BlockingQueue
来实现一个邮件系统（著名的qmail就是利用pipe技术构建的核心架构）。我们知道邮件有不同的级别，如果当前队列里有加急邮件需要处理的话，系统将优先处理加急邮件。
我们将以邮件传递为例子，说明PriorityBlockingQueue的使用方法。（注：这里的这个邮件模型只是一个非常简陋的模型，用来说明PriorityBlockingQueue的使用方法而已，
和实际应用有很大的差距）</p>
<p>首先，我们需要了解邮件传递过程的基本模型。在这个简单的邮件传送模型中涉及到下列概念
*MDA: Mail Deliver Agent, 负责接受指定用户的邮件。
*MTA: Mail Transfer Agent, 负责接受远程传送过来的邮件，并将其传送给收件人的MDA
它们和邮件用户之间的关系如下图</p>
<p><img src="http://blogsite.3322.org/page/download_attach?name=email.gif&amp;page_id=1929"  alt="" /></p>
其中MTA使用Queue传送邮件给MDA。因此，不同的用户会使用不同的Mail Queue。下面是MailQueue的代码
<pre>public class MailQueue&lt;E&gt; extends PriorityBlockingQueue&lt;E&gt;{<br />
public E take () throws InterruptedException {<br />
E ren  =super.take();<br />
Utils._log("take:"+ren);<br />
return ren;<br />
}<br />
<br />
public void put (E o) {<br />
super.put(o);<br />
Utils._log("put:"+o);<br />
}<br />
}<br />
</pre>
为了能够根据收件人的Mail Address找到相应的Mail Queue, 使用一个MailQueueFactory来产生MailQueue
<pre>public class MailQueueFactory {<br />
//A ConcurrentHashMap is used here instead of Hashtable<br />
static ConcurrentHashMap&lt;MailAccount,MailQueue&lt;Mail&gt;&gt; mailQueues  =<br />
new ConcurrentHashMap&lt;MailAccount,MailQueue&lt;Mail&gt;&gt;(); <br />
public static BlockingQueue&lt;Mail&gt; getMailQueue (MailDeliverer e) {<br />
return getMailQueue(e.getMailAccount());<br />
}<br />
<br />
public static BlockingQueue&lt;Mail&gt; getReceiveMailQueue (Mail m) {<br />
return getMailQueue (m.getReceiver());<br />
}<br />
<br />
public static BlockingQueue&lt;Mail&gt; getMailQueue (MailAccount e) {<br />
mailQueues.putIfAbsent  (e,new MailQueue&lt;Mail&gt;());<br />
MailQueue&lt;Mail&gt; mailQ   =mailQueues.get(e);<br />
<br />
return mailQ;<br />
}<br />
}<br />
</pre>
需要注意的是，我们在MailQueueFactory里面使用了ConcurrentHashMap，而不是传统的Hashtable, 虽然Hashtable是thread-safe，但是缺乏putIfAbsent这样的
原子函数，如果不小心设计的话，会造成对同一个MailQueue重复初始化，从而导致死锁问题。
下面看Mail的定义
<pre>public class Mail implements Comparable{<br />
public final static int emergencyMail  =0;<br />
public final static int normalMail     =1;<br />
<br />
static AtomicInteger serialCounter   =new AtomicInteger(0);<br />
<br />
private int mailLevel;<br />
private int serialNumber        =serialCounter.addAndGet(1);<br />
private MailAccount receiver    =null;<br />
private MailAccount sender      =null;<br />
private Date sendTime           =new Date();<br />
<br />
public Mail (String from, String to, int level) {<br />
...<br />
}<br />
<br />
//Get functions<br />
...<br />
<br />
public int compareTo(Object o) {<br />
if (o instanceof Mail) {<br />
return compareTo ((Mail)o);<br />
}<br />
return 0;<br />
}<br />
<br />
public int compareTo (Mail o) {<br />
if (o.mailLevel==this.mailLevel) {   //Same level, compare the serial no<br />
if (o.serialNumber==this.serialNumber)<br />
return 0;<br />
if (o.serialNumber&gt;this.serialNumber)<br />
return -1;<br />
return 1;<br />
}<br />
if (this.mailLevel==emergencyMail) return -1;<br />
return 1;<br />
}<br />
//Other functions<br />
...<br />
}<br />
</pre>
这里值得注意的是AtomicInteger的使用，它被用来做内部serialNumber的产生。另外就是compareTo函数的使用，PriorityBlockingQueue使用Comparable接口来判定元素的优先级别。这里所定义的优先级如下：
*如果邮件类别相同，则序列号小的邮件有较大的优先级
*如果邮件类别不同，则emergencyMail有较大的优先级
最后是Deliver Agent 和 Transfer Agent的代码
<pre>public class MailDeliverer {<br />
MailAccount mailAccount =null;<br />
<br />
public MailDeliverer (MailAccount account) {<br />
this.mailAccount    =account;<br />
}<br />
<br />
public MailAccount getMailAccount() {<br />
return mailAccount;<br />
}<br />
<br />
public Mail retrieveMail () {<br />
Mail mail   =null;<br />
while (mail==null) {<br />
try {<br />
mail    =MailQueueFactory.getMailQueue(this).take();<br />
}catch (Exception e) {<br />
Utils._log("Encounter Exception",e);<br />
}<br />
}<br />
return mail;<br />
}<br />
}<br />
<br />
public class MailTransfer {<br />
private static MailTransfer instance   =new MailTransfer ();<br />
private MailTransfer () { }<br />
<br />
public static MailTransfer getInstance () {<br />
return instance;<br />
}<br />
<br />
public void processMail (Mail m) {<br />
BlockingQueue mailQ =MailQueueFactory.getReceiveMailQueue(m);<br />
try {<br />
mailQ.put(m);<br />
} catch (InterruptedException e) {<br />
e.printStackTrace();<br />
}<br />
}<br />
}<br />
<br />
</pre>
</span></span>
<img src ="http://www.blogjava.net/java-blog/aggbug/156355.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/java-blog/" target="_blank">一步一步努力向上爬</a> 2007-10-27 16:23 <a href="http://www.blogjava.net/java-blog/articles/156355.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>