生产者-消费者模式
生產(chǎn)者/消費(fèi)者問題的多種Java實(shí)現(xiàn)方式
?
實(shí)質(zhì)上,很多后臺(tái)服務(wù)程序并發(fā)控制的基本原理都可以歸納為生產(chǎn)者/消費(fèi)者模式,而這是恰恰是在本科操作系統(tǒng)課堂上老師反復(fù)講解,而我們卻視而不見不以為然的。在博文《一種面向作業(yè)流(工作流)的輕量級(jí)可復(fù)用的異步流水開發(fā)框架的設(shè)計(jì)與實(shí)現(xiàn)》中將介紹一種生產(chǎn)者/消費(fèi)者模式的具體應(yīng)用。
生產(chǎn)者消費(fèi)者問題是研究多線程程序時(shí)繞不開的經(jīng)典問題之一,它描述是有一塊緩沖區(qū)作為倉庫,生產(chǎn)者可以將產(chǎn)品放入倉庫,消費(fèi)者則可以從倉庫中取走產(chǎn)品。解決生產(chǎn)者/消費(fèi)者問題的方法可分為兩類:(1)采用某種機(jī)制保護(hù)生產(chǎn)者和消費(fèi)者之間的同步;(2)在生產(chǎn)者和消費(fèi)者之間建立一個(gè)管道。第一種方式有較高的效率,并且易于實(shí)現(xiàn),代碼的可控制性較好,屬于常用的模式。第二種管道緩沖區(qū)不易控制,被傳輸數(shù)據(jù)對(duì)象不易于封裝等,實(shí)用性不強(qiáng)。因此本文只介紹同步機(jī)制實(shí)現(xiàn)的生產(chǎn)者/消費(fèi)者問題。
同步問題核心在于:如何保證同一資源被多個(gè)線程并發(fā)訪問時(shí)的完整性。常用的同步方法是采用信號(hào)或加鎖機(jī)制,保證資源在任意時(shí)刻至多被一個(gè)線程訪問。Java語言在多線程編程上實(shí)現(xiàn)了完全對(duì)象化,提供了對(duì)同步機(jī)制的良好支持。在Java中一共有四種方法支持同步,其中前三個(gè)是同步方法,一個(gè)是管道方法。
(1)wait() / notify()方法
(2)await() / signal()方法
(3)BlockingQueue阻塞隊(duì)列方法
(4)PipedInputStream / PipedOutputStream
本文只介紹最常用的前三種,第四種暫不做討論,有興趣的讀者可以自己去網(wǎng)上找答案。
?
一、wait() / notify()方法
wait() / nofity()方法是基類Object的兩個(gè)方法,也就意味著所有Java類都會(huì)擁有這兩個(gè)方法,這樣,我們就可以為任何對(duì)象實(shí)現(xiàn)同步機(jī)制。
wait()方法:當(dāng)緩沖區(qū)已滿/空時(shí),生產(chǎn)者/消費(fèi)者線程停止自己的執(zhí)行,放棄鎖,使自己處于等等狀態(tài),讓其他線程執(zhí)行。
notify()方法:當(dāng)生產(chǎn)者/消費(fèi)者向緩沖區(qū)放入/取出一個(gè)產(chǎn)品時(shí),向其他等待的線程發(fā)出可執(zhí)行的通知,同時(shí)放棄鎖,使自己處于等待狀態(tài)。
光看文字可能不太好理解,咱來段代碼就明白了:
[java] view plaincopyprint?看完上述代碼,對(duì)wait() / notify()方法實(shí)現(xiàn)的同步有了了解。你可能會(huì)對(duì)Storage類中為什么要定義public void produce(int num);和public void consume(int num);方法感到不解,為什么不直接在生產(chǎn)者類Producer和消費(fèi)者類Consumer中實(shí)現(xiàn)這兩個(gè)方法,卻要調(diào)用Storage類中的實(shí)現(xiàn)呢?淡定,后文會(huì)有解釋。我們先往下走。
?
二、await() / signal()方法
在JDK5.0之后,Java提供了更加健壯的線程處理機(jī)制,包括同步、鎖定、線程池等,它們可以實(shí)現(xiàn)更細(xì)粒度的線程控制。await()和signal()就是其中用來做同步的兩種方法,它們的功能基本上和wait() / nofity()相同,完全可以取代它們,但是它們和新引入的鎖定機(jī)制Lock直接掛鉤,具有更大的靈活性。通過在Lock對(duì)象上調(diào)用newCondition()方法,將條件變量和一個(gè)鎖對(duì)象進(jìn)行綁定,進(jìn)而控制并發(fā)程序訪問競(jìng)爭(zhēng)資源的安全。下面來看代碼:
[java] view plaincopyprint??
只需要更新倉庫類Storage的代碼即可,生產(chǎn)者Producer、消費(fèi)者Consumer、測(cè)試類Test的代碼均不需要進(jìn)行任何更改。這樣我們就知道為神馬我要在Storage類中定義public void produce(int num);和public void consume(int num);方法,并在生產(chǎn)者類Producer和消費(fèi)者類Consumer中調(diào)用Storage類中的實(shí)現(xiàn)了吧。將可能發(fā)生的變化集中到一個(gè)類中,不影響原有的構(gòu)架設(shè)計(jì),同時(shí)無需修改其他業(yè)務(wù)層代碼。無意之中,我們好像使用了某種設(shè)計(jì)模式,具體是啥我忘記了,啊哈哈,等我想起來再告訴大家~
?
三、BlockingQueue阻塞隊(duì)列方法
BlockingQueue是JDK5.0的新增內(nèi)容,它是一個(gè)已經(jīng)在內(nèi)部實(shí)現(xiàn)了同步的隊(duì)列,實(shí)現(xiàn)方式采用的是我們第2種await() / signal()方法。它可以在生成對(duì)象時(shí)指定容量大小。它用于阻塞操作的是put()和take()方法。
put()方法:類似于我們上面的生產(chǎn)者線程,容量達(dá)到最大時(shí),自動(dòng)阻塞。
take()方法:類似于我們上面的消費(fèi)者線程,容量為0時(shí),自動(dòng)阻塞。
關(guān)于BlockingQueue的內(nèi)容網(wǎng)上有很多,大家可以自己搜,我在這不多介紹。下面直接看代碼,跟以往一樣,我們只需要更改倉庫類Storage的代碼即可:
[java] view plaincopyprint?當(dāng)然,你會(huì)發(fā)現(xiàn)這時(shí)對(duì)于public void produce(int num);和public void consume(int num);方法業(yè)務(wù)邏輯上的實(shí)現(xiàn)跟前面兩個(gè)例子不太一樣,沒關(guān)系,這個(gè)例子只是為了說明BlockingQueue阻塞隊(duì)列的使用。
有時(shí)使用BlockingQueue可能會(huì)出現(xiàn)put()和System.out.println()輸出不匹配的情況,這是由于它們之間沒有同步造成的。當(dāng)緩沖區(qū)已滿,生產(chǎn)者在put()操作時(shí),put()內(nèi)部調(diào)用了await()方法,放棄了線程的執(zhí)行,然后消費(fèi)者線程執(zhí)行,調(diào)用take()方法,take()內(nèi)部調(diào)用了signal()方法,通知生產(chǎn)者線程可以執(zhí)行,致使在消費(fèi)者的println()還沒運(yùn)行的情況下生產(chǎn)者的println()先被執(zhí)行,所以有了輸出不匹配的情況。
對(duì)于BlockingQueue大家可以放心使用,這可不是它的問題,只是在它和別的對(duì)象之間的同步有問題。
轉(zhuǎn)載:http://blog.csdn.net/monkey_d_meng/article/details/6251879
總結(jié)
- 上一篇: Git 提交代码步骤总结
- 下一篇: UDT源码剖析(五):UDT::clea