春節回了趟老家,又體驗了一次流水席,由於桌席多,導致上菜慢,於是在等待間,總結了一下出菜流程的幾個特點:
1.有多個灶台,多個灶台都在同時做菜出來。
2.做出來的菜,會有專人用一個托盤端出來,每次端出來的菜(是同一個菜品)的數量不等。
3.由於端出來的菜可能不能滿足所有的桌數,所以,端菜人可能會隨機選擇幾桌(一般是就近原則,或者是主桌先端過去)上菜,其余的桌數繼續等待后面的端菜人出來。
以上3個條件,完全就是一個生產者消費者的場景,於是,把生產者消費者先來實現一下,然后再分析如何才能更快的上菜 :)
首先,我們把托盤給虛擬成一個資源池,表示這個托盤里是放菜的,當托盤里的菜大於1時,即有菜品被生產出來,端菜人就要端出去,當托盤里沒有菜時,外面所有的桌席都要等待:
(需要特別注意的是,這個資源池只能有一個實例化對象,就像托盤的數量是固定的一樣。)
public class ResourcePool { private int number = 0; public synchronized void producer(){ try { while(number==3){ this.wait(); } number++; System.out.println("producer: "+number); this.notifyAll(); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void consumer(){ try { while(number==0){ this.wait(); } number--; System.out.println("consumer: "+number); this.notifyAll(); } catch (InterruptedException e) { e.printStackTrace(); } } }
其實,我們要有灶台,這個灶台是專門做菜的,做出來的菜,當然是全部放在了資源池(即托盤中),灶台是會有多個的,所以要繼承thread類:
public class ResourceProduce extends Thread{ private ResourcePool rp; public ResourceProduce(ResourcePool rp) { this.rp = rp; } public void run() { rp.producer(); } }
托盤中有了菜,就得端出去了,給送到外面的桌席上去,由於桌席是多桌,所以,也要繼承thread類:
public class ResourceConsumer extends Thread{ private ResourcePool rp; public ResourceConsumer(ResourcePool rp) { this.rp = rp; } public void run() { rp.consumer(); } }
這些基礎的設施都准備好后,我們的端菜人就出來了:
public class ResourceUtil { public void resource(){ ResourcePool rp = new ResourcePool(); for (int i = 0; i < 3; i++) { new ResourceProduce(rp).start(); } for (int i = 0; i < 5; i++) { new ResourceConsumer(rp).start(); } } public static void main(String[] args) { ResourceUtil ru = new ResourceUtil(); ru.resource(); } }
我們來看一下最后的輸出結果:
當只有三個灶台,而桌席有5桌時,程序就等待下去了,於是,當我們把灶台數改成5后,運行結果:
producer: 1 producer: 2 producer: 3 consumer: 2 producer: 3 consumer: 2 producer: 3 consumer: 2 consumer: 1 consumer: 0
通過上面的程序運行,如果想上菜速度快,還是得加灶台,多加廚師,當然,這只是就這個場景簡單的分析了一下,可能還會有更復雜的因素沒考慮到,舉這個例子的主要意思,是想讓多多的理解一下生產者消費者模式,該模式我們平常可能用原生的比較少,但其實使用的場景一直都在用,比如線程池,連接池,等等。所以,知其然也知其所以然也很有必要,我們接着就代碼來說明一下這個實現代碼中的重點:
1.資源池有且只有一個。
2.synchronized,是鎖對象,簡單說一下:一個對象有且只有一把鎖,當有多個synchronized方法或代碼塊都向該對象申請鎖時,在同一時間,只會有一個線程得到該鎖並運行,其它的就被阻塞了。
3.wait,是指該線程等待,wait有一個很重要的點,就是釋放鎖,上面也說了synchronized在同一時間只會有一個線程得到該鎖並運行,所以,一旦wait后,就會釋放鎖,但當前線程等待下去,其它的線程再競爭這把鎖。
4.notifyAll是指喚醒當前對象的所有等待的線程。
5.所有喚醒的線程會同時去競爭這把鎖,但是JVM會隨機選擇一個線程並分配這把鎖給該線程。
6.上面的synchronized wait notifyAll都是對一個對象進行操作,但這三個都是用在了資源池的類里面,所以,這也是資源池有且只能有一個的原因。
后緒:至於生產者消費者能給我們測試帶來什么樣的幫助,我暫時還沒想到,但了解一下,出去面試時,有很大的可能性會被問到,有興趣的,就當作一種知識儲備吧。