多線程程序設計學習(6)Producer-Consumer模式


Producer-Consumer【生產消費者模式】
一:Producer-Consumer pattern的參與者
--->產品(蛋糕)
--->通道(傳遞蛋糕的桌子)
--->生產者線程(制造蛋糕的線程)
--->消費者線程(吃掉蛋糕的線程)


       

二:Producer-Consumer pattern模式什么時候使用
--->大量生產+大量消費的模式


三:Producer-Consumer pattern思考
--->【生產消費者模式】,肩負保護數據安全性使命的是通道參與者。通道參與者參與進行線程間的共享互斥,讓生產着能正確將數據傳遞到消費者手中。
--->通道(桌子)的put和take方法都使用了【獨木橋模式】,而生產者和消費者都不想依賴table類的詳細實現,也就說,生產者不必理會其他線程,只管生產並put,同樣消費者也不必理會其他線程,只管take就好。而線程的共享互斥,synchronized,wati,notifyAll這些考慮的多線程操作的代碼,全都隱藏在通道table類里。提高了table的復用性。

--->生產者消費者模式,存在兩方處理速率不同的話,必然造成一方等待,或占用通道大量內存的問題。


--->多線程合作的口決
        線程的合作要想:放在中間的東西
        線程的互斥要想:應該保護的東西

--->多生產者對單一消費者,如果情況合理,一方可以不用考慮互斥,就不用加鎖,提升性能。

四進階說明
--->習慣編寫java多線程。當所調用的方法拋出,或內部抓住異常:InterruptedException.
        ==>通常傳遞給我們兩個信息。(1)這是‘需要花點時間’的方法(2)這是‘可以取消’的方法
        ==>告訴我們方法內部有這三個選手:java.lang.Object類里的wait方法        
                                                                                     java.lang.Thread類里的sleep方法
                                                                                     java.lang.Thread類里的join方法
                
--->需要花點時間的方法
        wait==>執行wait方法的線程,進入wait set里休眠,等待notify,notifyAll喚醒。在休眠期間不會活動,因此需要花費時間。
        sleep==>執行sleep,會暫停執行參數內所設置的時間,這也是需要花費時間
        join==>會等待制定的線程結束為止。才執行本線程。也就是花費直到制定線程結束之前的這段時間

--->可以取消的方法。
        因為需要花費時間,會降低程序的響應性,所以我們會希望像下面這樣可以在中途放棄(取消)執行這個方法
        1取消wait方法等待notify,notifyAll喚醒的操作
        2取消sleep方法等待設置長度時間的操作
        3取消join方法等待其他線程結束的操作

--->取消線程等待的詳細解說
       (1) A線程的實例為athread,線程體內部執行Thread.sleep(1000)時,想取消其睡眠狀態。則需要B線程中取消。
                --->在B線程中用athread.interrupt().
                --->則A線程終止睡眠,並拋出或被抓住InterruptedException
                --->這個時候A線程的catch塊的代碼,至關重要。
      (2) A線程的實例為athread,線程體內部執行wait()時,想取消等待notify,notifyAll喚醒的操作。則需要B線程中取消。
                --->在B線程體中用athread.interrupt().
                --->則A線程終止等待狀態,並嘗試重新獲取鎖定。
                --->獲取鎖定后,拋出或被抓住InterruptedException
                --->這個時候A線程的catch塊的代碼,至關重要。
      (3)A線程的實例為athread,線程體內部執行join(),想等待其他線程執行結束。則需要B線程中取消。
                 --->在B線程中用athread.interrupt().
                --->則A線程終止睡眠,並拋出或被抓住InterruptedException
                --->這個時候A線程的catch塊的代碼,至關重要。


--->線程對象.interrupt(),Thead.interrupted,線程對象.isinterrupted()區別
        ==>interrupt()讓等待或休眠的線程變成中斷狀態,拋出異常
        ==>interrupted()檢查線程的中斷狀態
                                        是中斷狀態,返回true,並將中斷狀態修改成非中斷狀態
                                        不是中斷狀態,返回false,不做任何操作
        ==>isinterrupted簡單的檢查線程是否為中斷狀態,是返回true,不是返回false,不做任何操作



     Producer-Consumer案例
        三個生產蛋糕的線程,三個消費蛋糕的線程,一個傳遞蛋糕的桌子。

傳遞蛋糕的桌子

 1 package com.yeepay.sxf.thread5;
 2 /**
 3  * 在消費線程和生產線程中間起傳遞作用的桌子
 4  * @author sxf
 5  *
 6  */
 7 
 8 public class Table {
 9     //存放蛋糕的數組
10     private String[] cakes;
11     //下一個放蛋糕的位置
12     private int nextPut;
13     //下一個取蛋糕的位置
14     private int nextGet;
15     //蛋糕數組中蛋糕的數量
16     private int count;
17     //構造器
18     public Table (int count){
19         this.cakes=new String[count];
20         this.nextGet=0;
21         this.nextPut=0;
22     }
23     //存放蛋糕
24     public synchronized  void putCakes(String cake) throws InterruptedException{
25         System.out.println("["+Thread.currentThread().getName()+"]put"+cake);
26         //警戒條件  如果桌子上蛋糕,慢了,就阻塞生產線程。
27         while (count>=cakes.length) {
28             wait();            
29         }
30         //將蛋糕放入模擬隊列
31         cakes[nextPut]=cake;
32         //算出下一個放蛋糕的位置
33         nextPut=(nextPut+1)%cakes.length;
34         //蛋糕數據量加1
35         count++;
36         //喚醒別的線程
37         notifyAll();
38     }
39     
40     //取蛋糕
41     public synchronized String takeCake() throws InterruptedException{
42         //判斷桌子上是否有蛋糕,如果沒有,阻塞線程
43         while (count<=0) {
44             wait();
45         }
46         //取出蛋糕
47         String cake=cakes[nextGet];
48         //計算出下一個取蛋糕的位置
49         nextGet=(nextGet+1)%cakes.length;
50         //蛋糕數量減一
51         count--;
52         //喚醒其他線程
53         notifyAll();
54         System.out.println("【"+Thread.currentThread().getName()+"】get"+cake);
55         return cake;
56     }
57 }
View Code

生產蛋糕的線程

 1 package com.yeepay.sxf.thread5;
 2 /**
 3  * 制造蛋糕線程
 4  * @author sxf
 5  *
 6  */
 7 public class MakeCakeThread implements Runnable{
 8     //存放蛋糕的桌子
 9     private Table table;
10     
11     //構造器
12     public  MakeCakeThread(Table table) {
13         this.table=table;
14     }
15 
16     @Override
17     public void run() {
18         while (true) {
19             for (int i = 0; i <100; i++) {
20                 
21                 try {
22                     //生產蛋糕,並放入
23                     table.putCakes(Thread.currentThread().getName()+"的蛋糕"+i);
24                     //當前線程休息1秒鍾
25                 
26                     Thread.sleep(1000);
27                 } catch (InterruptedException e) {
28                     // TODO Auto-generated catch block
29                     e.printStackTrace();
30                 }
31             }
32             
33         }
34         
35     }
36 
37     
38 }
View Code

吃掉蛋糕的線程

 1 package com.yeepay.sxf.thread5;
 2 /**
 3  * 吃蛋糕的線程
 4  * @author sxf
 5  *
 6  */
 7 public class EatCakeThread implements Runnable {
 8     //桌子
 9     private  Table table;
10     //構造器
11     public EatCakeThread(Table table){
12         this.table=table;
13     }
14     /**
15      * 線程體
16      */
17     @Override
18     public void run() {
19         while(true){
20             try {
21                 String cake=table.takeCake();
22                 Thread.sleep(500);
23             } catch (InterruptedException e) {
24                 // TODO Auto-generated catch block
25                 e.printStackTrace();
26             }
27         }
28         
29     }
30     
31 }
View Code

測試類

 1 package com.yeepay.sxf.thread5;
 2 /**
 3  * 測試類
 4  * @author sxf
 5  *
 6  */
 7 public class Test {
 8 
 9     public static void main(String[] args) {
10         //聲明一張桌子
11         Table table=new Table(4);
12         //聲明生產蛋糕的線程
13         Thread makeThread1=new Thread(new MakeCakeThread(table));
14         makeThread1.setName("尚曉飛師傅");
15         Thread makeThread2=new Thread(new MakeCakeThread(table));
16         makeThread2.setName("范林軍師傅");
17         Thread makeThread3=new Thread(new MakeCakeThread(table));
18         makeThread3.setName("黃栓林師傅");
19         
20         //聲明吃蛋糕的線程
21         Thread eatThread1=new Thread(new EatCakeThread(table));
22         eatThread1.setName("顧客1");
23         Thread eatThread2=new Thread(new EatCakeThread(table));
24         eatThread2.setName("顧客2");
25         Thread eatThread3=new Thread(new EatCakeThread(table));
26         eatThread3.setName("顧客3");
27         
28         //啟動線程
29         makeThread1.start();
30         makeThread2.start();
31         makeThread3.start();
32         eatThread1.start();
33         eatThread2.start();
34         eatThread3.start();
35         
36         
37     }
38 }
View Code

測試結果:

[尚曉飛師傅]put尚曉飛師傅的蛋糕0
[范林軍師傅]put范林軍師傅的蛋糕0
【顧客1】get尚曉飛師傅的蛋糕0
【顧客2】get范林軍師傅的蛋糕0
[黃栓林師傅]put黃栓林師傅的蛋糕0
【顧客3】get黃栓林師傅的蛋糕0
[尚曉飛師傅]put尚曉飛師傅的蛋糕1
【顧客3】get尚曉飛師傅的蛋糕1
[范林軍師傅]put范林軍師傅的蛋糕1
【顧客1】get范林軍師傅的蛋糕1
[黃栓林師傅]put黃栓林師傅的蛋糕1
【顧客2】get黃栓林師傅的蛋糕1
[尚曉飛師傅]put尚曉飛師傅的蛋糕2

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com