浅谈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

相关文章
相关标签/搜索