淺談Java多線程(二)

線程的協調設計模式

在Java多線程中,對於線程之間共享的實例資源,能夠經過synchronized修飾符修飾其方法,實現線程之間的同步。另外,在多線程設計中,還需考慮到線程之間的協調。關於協調的一個典型設計模式即是Producer–Consumer(生產者-消費者)模式。
 
Producer–Consumer(生產者-消費者)模式

在這一模式中,存在Producer和Consumer兩類線程,Producer線程用於生成Data(共享數據資源),而Consumer線程用於消費Data,在Producer和Consumer之間,存在對於Data生成和消費的協調,即當不存在Data時,Consumer線程須要等待Producer線程生成新的Data,而當Data過多時,Producer線程須要等待Consumer線程消費過多的Data。在Producer–Consumer模式中,引入了Channel(管道)類,負責Data在各線程之間的協調。Producer–Consumer模式的UML類圖以下所示。數組

在上圖中,Producer線程類和Consumer線程類均包含對Channel類對象的引用,而Channel類對象封裝了Data類,並分別實現了生產和消費Data的同步方法produce和consume。在produce和consume方法中,經過使用wait和notifyAll方法進一步實現Data的協調。多線程

 
wait、notify、notifyAll
在Java中,wait、notify、notifyAll是Object類的方法,用於實現對調用對象方法的線程的暫停和喚醒。wait用於暫停線程,將其放入對象的wait set(線程等待集合),而notify、notifyAll方法用於喚醒wait set中的線程,使其繼續執行。notify和notifyAll的不一樣是,當wait set中存在多個線程時,notify只會從中隨機喚醒一個線程,而notifyAll會從中喚醒全部線程,由其進行競爭,得到同步鎖並繼續執行。

一個關於wait、notify、notifyAll使用的簡單示例如圖所示。ide

Producer–Consumer(生產者-消費者)模式的實現示例函數

Producer和Consumer線程類
 
  
  
  
  
  1. package com.wt.pc;  
  2.  
  3. public class Producer extends Thread{  
  4.  
  5.     //對Channel對象的引用  
  6.     Channel channel = null;  
  7.       
  8.     //Consumer類構造函數  
  9.     public Producer(String producerName,Channel channel)  
  10.     {  
  11.         super(producerName);  
  12.         this.channel = channel;  
  13.     }  
  14.       
  15.     //Producer線程每隔500ms嘗試生成新的data  
  16.     public void run()  
  17.     {  
  18.         try{  
  19.             while(true)  
  20.             {  
  21.                 channel.produce(Integer.toString(Channel.dataId++));  
  22.                 Thread.sleep(500);  
  23.             }  
  24.         }catch(Exception e){}  
  25.           
  26.     }  
  27. }  
 
  
  
  
  
  1. package com.wt.pc;  
  2.  
  3. public class Consumer extends Thread{  
  4.  
  5.     //對Channel對象的引用  
  6.     Channel channel = null;  
  7.       
  8.     //Consumer類構造函數  
  9.     public Consumer(String consumerName,Channel channel)  
  10.     {  
  11.         super(consumerName);  
  12.         this.channel = channel;  
  13.     }  
  14.       
  15.     //Consumer線程每隔500ms嘗試消費data  
  16.     public void run()  
  17.     {  
  18.         try{  
  19.             while(true)  
  20.             {  
  21.                 channel.consume();  
  22.                 Thread.sleep(500);  
  23.             }  
  24.         }catch(Exception e){}  
  25.           
  26.     }  
  27.  
  28. }  
Channel和Data類
 
  
  
  
  
  1. package com.wt.pc;  
  2.  
  3. public class Channel {  
  4.     //靜態變量,用於生成data名稱  
  5.     static int dataId = 0;  
  6.       
  7.     //存儲data的數組  
  8.     Data dataList[];  
  9.     //當前未消費data的頭序號  
  10.     int head;  
  11.     //當前未消費data的尾序號的下一個  
  12.     int tail;  
  13.     //當前未消費data的數目  
  14.     int count;  
  15.       
  16.     //Channel類構造函數  
  17.     //數組容量爲3,其餘值初始化爲0  
  18.     public Channel()  
  19.     {  
  20.         dataList = new Data[3];  
  21.         head = 0;  
  22.         tail = 0;  
  23.         count = 0;  
  24.     }  
  25.  
  26.     //produce方法,用於生成data  
  27.     public synchronized void produce(String dataName)throws Exception{  
  28.         //當數組容量已滿時,即不能再生成新data時,當前線程進入wait set  
  29.         while (count>=3)  
  30.         {  
  31.             wait();  
  32.         }  
  33.         //生成新的data  
  34.         System.out.println(Thread.currentThread().getName()+" is producing "+dataName);  
  35.         dataList[tail] = new Data();  
  36.         dataList[tail].setDataName(dataName);  
  37.         tail = (tail+1)%3;  
  38.         count++;  
  39.         Thread.sleep(400);  
  40.         //喚醒wait set中的線程  
  41.         notifyAll();  
  42.     }  
  43.       
  44.     //consume方法,用於消費data  
  45.     public synchronized void consume()throws Exception{  
  46.         //當數組容量爲空時,即不能再消費data時,當前線程進入wait set  
  47.         while (count<=0)  
  48.         {  
  49.             wait();  
  50.         }  
  51.         //消費data  
  52.         System.out.println(Thread.currentThread().getName()+" is consuming "+dataList[head].getDataName());  
  53.         head = (head+1)%3;  
  54.         count--;  
  55.         Thread.sleep(300);  
  56.         //喚醒wait set中的線程  
  57.         notifyAll();          
  58.     }     
  59. }  
 
  
  
  
  
  1. package com.wt.pc;  
  2. public class Data {  
  3.     String dataName;  
  4.  
  5.     public String getDataName() {  
  6.         return dataName;  
  7.     }  
  8.  
  9.     public void setDataName(String dataName) {  
  10.         this.dataName = dataName;  
  11.     }  
  12. }  
主函數
 
  
  
  
  
  1. public static void main(String args[])  
  2. {  
  3.     Channel channel = new Channel();  
  4.     new Producer("p1",channel).start();  
  5.     new Producer("p2",channel).start();  
  6.     new Producer("p3",channel).start();  
  7.     new Consumer("c1",channel).start();  
  8.     new Consumer("c2",channel).start();  
  9.     new Consumer("c3",channel).start();  
  10.       
 
執行結果
p2 is producing 0
c2 is consuming 0
p3 is producing 2
p1 is producing 1
c3 is consuming 2
c1 is consuming 1
p1 is producing 5
p3 is producing 4
c2 is consuming 5
p2 is producing 3
p3 is producing 7
c1 is consuming 4
c3 is consuming 3
p3 is producing 9
p1 is producing 6
c2 is consuming 7
p2 is producing 8
c3 is consuming 9
c1 is consuming 6
p2 is producing 12
p3 is producing 10
c2 is consuming 8
......
從執行結果中能夠看出,生成Data線程的執行次數p和消費Data線程的執行次數c始終知足:
p>=c且p<=c+3
即保證Data數組在消費時存在Data但也不超過數組容量,從而有效實現Producer和Consumer線程類關於Data的協調。
 
總結

在Java多線程設計中,須要充分考慮線程之間的同步和協調。針對不一樣的應用場景,能夠採用不一樣的設計模式,已有的設計模式有Single Threaded Execution、Immutable、Guarded Suspension、Balking、Producer-Consumer、Read-Write Lock、Thread-Per-Message、Worker Thread等,具體可進一步參考網上有關「Java多線程設計模式」的教程。this

相關文章
相關標籤/搜索