Java_線程—經典的例子:生產者和消費者

線程,還有一個經常出現的例子:生產者和消費者。

一個生產,一個消費,中間就是產品,一個產品只能被一個生產者生產,同樣,也只能被一個消費者消費。

既然是生產和消費,這就好理解了,現在也快中秋了,舉個例子:生產月餅的廠家和消費者。廠家負責生產,消費者進行消費。廠家不斷的生產,消費者就不斷的消費,這有個前提,廠家生產月餅在前,消費者消費月餅在後,不能說消費者先消費月餅,然後廠家再去生產吧,這明顯就不靠譜。還有一點,廠家生產蓮蓉蛋黃口味的月餅,消費者是不是隻能消費蓮蓉口味的月餅,而不能消費五仁口味的月餅,對吧。這點搞清楚了,我們就來寫個小程序:


一氣呵成,全部代碼都在這裏了。整個代碼也很好理解。

line14-16是一個月餅類,月餅只定義了一個屬性,那就是口味,kind。

line18-46是生產月餅的線程,線程需要傳遞進去一個月餅,然後對月餅進行生產。其中,根據不同生產的編號,相應生產不同口味的月餅。

line28-66是消費月餅的線程,消費月餅也需要傳遞一個月餅過去。消費很簡單,就打印出月餅的口味。

最後是main函數,new一個月餅對象,然後一個生產月餅的線程和一個消費月餅的線程,然後啓動。

看完代碼來看看運行的結果。


代碼運行的結果如上,爲什麼要給出兩個結果呢,因爲線程的執行是不是具有不確定性?你能確定是哪個線程搶到CPU的執行權嗎?對吧。<如果你嘗試着寫完後運行總是結果一的話,那麼你多運行幾次,或者你把怒罵改大一點,就比較明顯。>

先來看結果一,結果一一次把全部月餅都給生產完了,然後再進行消費,這點是沒錯的,先生產後消費嘛。但你仔細看看,生產是沒錯的,分別生產不同口味的月餅,但看消費,消費就奇怪了,5次消費的都是同一種口味的月餅,這就很明顯不對了嘛。(如果你給月餅加個編號屬性的話,你就會發現,消費的都是最後生產的那個月餅)

再來看看結果二,結果二中,兩個線程就有點交叉了,但你仔細一看,還是不對,消費的時候還是出錯了。


這問題該怎麼解決呢?

同步,加鎖。生產的時候,如果月餅是生產好的,那麼就等待,等消費者消費完後再生產,同時,生產完畢後要通知消費者去消費,同樣的道理,消費者消費的時候先看一下這月餅是不是 已經生產好了,如果生產好了就消費,同時,消費完畢後通知生產者繼續生產下一個產品。


下面對上面的小例子進行改進。

看代碼:


代碼不詳細解釋了,只解釋一下改動的部分,

其中,月餅類,加多了一個標記,標記月餅是否已經生產好。

其次,生產月餅的線程和消費月餅的線程加了同步和鎖,代碼中也有相應的解釋,這裏就不再重複了。

最後看打印的結果,是不是老老實實的按照我們原來定的規則進行生產和消費呢?

別不信,不信你可以多運行幾次這段代碼,看看結果是否一致。


既然都按照我們的要求生產了,這不就完事了麼,別,還沒完。發現沒有,這代碼有嚴重的缺陷,就是我們生產和消費的線程裏面加鎖,那麼我們每次寫線程的時候是不是都要加鎖呢?我們能不能夠在月餅類裏面定義一個生產和消費的方法,把鎖和同步加到月餅類裏面去呢?這樣是不是比較方便呢?不用每次生產和每次消費的時候都去加鎖,而且還有一個缺點是:如果我生產和消費的時候加的不是同一把鎖會出現什麼情況呢?對吧,這些都是需要考慮的。

下面對上面這段代碼進行優化。


這是優化後的代碼,你會發現,鎖沒了,同步代碼塊也好像沒了,但方法卻多了個同步,對,就是這樣。我們在生產和消費的方法中加了同步,其他還是一樣,生產前先判斷是否已經生產好,生產完畢後改標記後通知消費者消費,同理,消費者也一樣,先判斷是否已經有了,然後再消費,消費完後通知生產者生產。最後生產和消費的線程是不是變得很簡單,只需要調用月餅類的生產和消費的方法即可;


代碼到這裏看起來好像要結束了。

不不不,還沒結束,你發現沒,同步是我們自己寫的,java裏面有沒有已經寫好的呢?對吧,如果有的話,就直接用唄,還需要那麼麻煩自己去寫麼?還有一點,你發現沒,我最後notify,其實是通知了全部線程,也就是說,不論是生產還是消費的線程都通知了,我們能不能夠做到,生產完只通知消費者,消費完只通知生產者呢?

下面來看最終的代碼:


代碼只是月餅類做了改動,其他都沒動,還是原來的樣子。現在來說說代碼的不同之處;

line22,new了一個鎖,line23-24,分別new了兩個生產和消費的狀態。

生產:生產的時候先鎖上,然後判斷是否已經有,如果有,那麼生產的Condition等待,如果沒有就進入生產,生產完後通知消費者消費,最後釋放鎖。

消費:消費的時候先鎖上,然後判斷是否已經有,如果沒有就等待,如果有就消費,消費完後通知生產者生產。

這裏要注意的是:引入Lock和Condition的時候。等待是await,而不是wait,通知不再是notify,而是signal。



好了,線程篇先寫到這裏,後續再補充其他吧!

線程篇:

Java_線程-Thread

Java_線程—經典的例子:售票

Java_線程—經典的例子:生產者和消費者


java_多線程下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章