多线程间通信——生产者消费者问题

多线程间通信——生产者消费者问题

生产和消费同时进行,需要多线程。但是执行的任务不相同,处理的资源却是相同的:线程间的通信。

1、存在的问题及解决

期望生产一个商品就被消费掉,再生产下一个商品。

 1 /**
 2  * 案例:期望生产一个商品就被消费掉,再生产下一个商品。
 3  */
 4 //1、描述资源
 5 class Resource{
 6     private String name;
 7     private int count=1;
 8     public Object lock=new Object();
 9     public void set(String name) throws InterruptedException {
10         //给成员变量赋值并加上编号
11         this.name = name+count;
12         //编号自增
13         count++;
14         //打印生产了哪个商品
15         Thread.sleep(500);
16         System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
17     }
18     
19     public void out() {
20         System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name);
21     }
22 }
23 
24 //2、描述生产者
25 class Producer implements Runnable{
26     private Resource r;
27     public Producer(Resource r){
28         this.r=r;
29     }
30     @Override
31     public void run() {
32         while(true) {
33                 try {
34                     r.set("面包");
35                 } catch (InterruptedException e) {
36                     // TODO Auto-generated catch block
37                     e.printStackTrace();
38                 }        
39         }
40     }    
41 }
42 //3、描述消费者
43 class Consumer implements Runnable{
44     private Resource r;
45     public Consumer(Resource r){
46         this.r=r;
47     }
48     @Override
49     public void run() {
50         while(true) {
51                 r.out();
52         }
53     }    
54 }
55 
56 public class ThreadMain {
57     public static void main(String[] args) {
58         //1、创建资源对象
59         Resource r=new Resource();
60         //2、创建线程任务
61         Producer p=new Producer(r);
62         Consumer c=new Consumer(r);
63         //3、创建线程对象
64         Thread t1=new Thread(p);
65         Thread t2=new Thread(c);
66         
67         t1.start();
68         t2.start();
69     }
70 }
View Code

出现的问题;

问题一:消费早期商品。使用同步函数:将set方法和out方法加上synchronized关键字,问题解决。

 1 /**
 2  * 案例:期望生产一个商品就被消费掉,再生产下一个商品。
 3  * 问题一:消费早期商品。使用同步函数:将set方法和out方法加上synchronized关键字,问题解决。
 4  * 问题二:同一个商品重复消费,疯狂生产和疯狂消费。
 5  *
 6  */
 7 //1、描述资源
 8 class Resource{
 9     private String name;
10     private int count=1;
11     public Object lock=new Object();
12     public synchronized void set(String name) throws InterruptedException {
13         //给成员变量赋值并加上编号
14         this.name = name+count;
15         //编号自增
16         count++;
17         //打印生产了哪个商品
18         Thread.sleep(500);
19         System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
20     }
21     
22     public synchronized void out() {
23         System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name);
24     }
25 }
26 
27 //2、描述生产者
28 class Producer implements Runnable{
29     private Resource r;
30     public Producer(Resource r){
31         this.r=r;
32     }
33     @Override
34     public void run() {
35         while(true) {
36                 try {
37                     r.set("面包");
38                 } catch (InterruptedException e) {
39                     // TODO Auto-generated catch block
40                     e.printStackTrace();
41                 }        
42         }
43     }    
44 }
45 //3、描述消费者
46 class Consumer implements Runnable{
47     private Resource r;
48     public Consumer(Resource r){
49         this.r=r;
50     }
51     @Override
52     public void run() {
53         while(true) {
54                 r.out();
55         }
56     }    
57 }
58 
59 public class ThreadMain {
60     public static void main(String[] args) {
61         //1、创建资源对象
62         Resource r=new Resource();
63         //2、创建线程任务
64         Producer p=new Producer(r);
65         Consumer c=new Consumer(r);
66         //3、创建线程对象
67         Thread t1=new Thread(p);
68         Thread t2=new Thread(c);
69         
70         t1.start();
71         t2.start();
72     }
73 }
View Code

问题二:同一个商品重复消费,疯狂生产和疯狂消费。

分析:生产者什么时候生产呢?当容器中没有面包时,就生产,否则不生产;

消费者什么时候消费呢?当容器中有面包时,就消费,否则不消费。

生产者生产了商品后,通知消费者来消费,这时的生产者应该处于等待状态;

消费者消费了商品后,通知生产者生产,这时消费者应该处于等待状态。

等待:wait();  通知:notify();

以下代码中标红的为增加修改的部分。

 1 /**
 2  * 案例:期望生产一个商品就被消费掉,再生产下一个商品。
 3  */
 4 //1、描述资源
 5 class Resource{
 6     private String name;
 7     private int count=1;
 8     //定义标记
 9     private boolean flag=false;
10     public synchronized void set(String name) {
11         if(flag) {
12             try {wait();} catch (InterruptedException e) {}
13         }
14         //给成员变量赋值并加上编号
15         this.name = name+count;
16         //编号自增
17         count++;
18         //打印生产了哪个商品
19         System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
20         flag=true;
21         //唤醒消费者
22         notify();
23     }
24     
25     public synchronized void out() {
26         if(!flag) {
27             try {wait();} catch (InterruptedException e) {}
28         }
29         System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name);
30         flag=false;
31         //唤醒生产者
32         notify();
33     }
34 }
35 
36 //2、描述生产者
37 class Producer implements Runnable{
38     private Resource r;
39     public Producer(Resource r){
40         this.r=r;
41     }
42     @Override
43     public void run() {
44         while(true) {
45                 r.set("面包");        
46         }
47     }    
48 }
49 //3、描述消费者
50 class Consumer implements Runnable{
51     private Resource r;
52     public Consumer(Resource r){
53         this.r=r;
54     }
55     @Override
56     public void run() {
57         while(true) {
58                 r.out();
59         }
60     }    
61 }
62 
63 public class ThreadMain2 {
64     public static void main(String[] args) {
65         //1、创建资源对象
66         Resource r=new Resource();
67         //2、创建线程任务
68         Producer p=new Producer(r);
69         Consumer c=new Consumer(r);
70         //3、创建线程对象
71         Thread t1=new Thread(p);
72         Thread t2=new Thread(c);
73         
74         t1.start();
75         t2.start();
76     }
77 }

 

2、等待唤醒机制

wait(): 会让线程处于等待状态,其实就是将线程临时存储到了线程池中。
    
当前线程必须拥有此对象的监视器(锁),否则抛出java.lang.IllegalMonitorStateException

notify(): 会唤醒线程池中任意一个等待的线程。

notifyAll(): 会唤醒线程池中所有的等待的线程。

 这些方法必须使用在同步中,因为必须要标识wait、notify等方法所属的锁。同一个锁上的notify,只能唤醒改锁上wait的线程。默认是this.wait();this.notify();this.notifyAll()。

 为什么这些方法定义在Object类中,而不是Thread类中呢?

因为这些方法必须标识所属的锁,而锁可以是任意对象,任意对象可以调用的方法必然是Object类中的方法。

 3、多生产多消费问题及解决方案

 1 /**
 2  * 案例:多生产多消费问题
 3  * 问题一:生产了商品没有被消费,同一个商品被消费多次
 4  * 产生问题原因:被唤醒的线程没有判断标记,造成问题一的产生。
 5  * 解决:将if判断改为while.只要是多生产,必须是while判断条件。
 6  * 
 7  * 问题二:while判断后死锁
 8  * 原因:生产方唤醒了生产方的线程。本方唤醒了本方。
 9  * 解决方法:希望本方唤醒对方。没有对应方法,则唤醒所有。
10  * 但是唤醒所有效率有点低。
11  */
12 
13 //1、描述资源
14 class Resource{
15     private String name;
16     private int count=1;
17     //定义标记
18     private boolean flag=false;
19     public synchronized void set(String name) {//t1,t2
20         while(flag) {
21             try {wait();} catch (InterruptedException e) {}
22         }
23         //给成员变量赋值并加上编号
24         this.name = name+count;
25         //编号自增
26         count++;
27         //打印生产了哪个商品
28         System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
29         flag=true;
30         //唤醒消费者
31         notifyAll();
32     }
33     
34     public synchronized void out() {//t3,t4
35         while(!flag) {
36             try {wait();} catch (InterruptedException e) {}
37         }
38         System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name);
39         flag=false;
40         //唤醒生产者
41         notifyAll();
42     }
43 }
44 
45 //2、描述生产者
46 class Producer implements Runnable{
47     private Resource r;
48     public Producer(Resource r){
49         this.r=r;
50     }
51     @Override
52     public void run() {
53         while(true) {
54                 r.set("面包");        
55         }
56     }    
57 }
58 //3、描述消费者
59 class Consumer implements Runnable{
60     private Resource r;
61     public Consumer(Resource r){
62         this.r=r;
63     }
64     @Override
65     public void run() {
66         while(true) {
67                 r.out();
68         }
69     }    
70 }
71 
72 public class ThreadMain3 {
73     public static void main(String[] args) {
74         //1、创建资源对象
75         Resource r=new Resource();
76         //2、创建线程任务
77         Producer p=new Producer(r);
78         Consumer c=new Consumer(r);
79         //3、创建线程对象
80         Thread t1=new Thread(p);
81         Thread t2=new Thread(c);
82         Thread t3=new Thread(p);
83         Thread t4=new Thread(c);
84         
85         t1.start();
86         t2.start();
87         t3.start();
88         t4.start();
89     }
90 }
ThreadMain3

4、多线程间通信-jdk1.5-Lock接口

jdk1.5以后提供多生产多消费的解决方案。在java.util.concurrent.locks软件包中提供了解决方案。

Lock接口:Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。

随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:

     Lock l = ...; 
     l.lock();
     try {
         // access the resource protected by this lock
     } finally {
         l.unlock();
     }

Lock方法:
void lock()  获取锁。
void lockInterruptibly()   如果当前线程未被中断,则获取锁。
Condition newCondition()   返回绑定到此 Lock 实例的新 Condition 实例。
 boolean tryLock()  仅在调用时锁为空闲状态才获取该锁。
 boolean tryLock(long time, TimeUnit unit)   如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。
 void unlock()   释放锁


 多生产多消费的效率问题解决: 

  1 import java.util.concurrent.locks.Condition;
  2 import java.util.concurrent.locks.Lock;
  3 import java.util.concurrent.locks.ReentrantLock;
  4 
  5 /**
  6     jdk1.5将原有的监视器方法wait(),notify(),notifyAll封装到Condition对象中。
  7     Condition对象的出现其实是替代了Object中的监视器方法。
  8     await();    signal();    signalAll();
  9     旧版中唤醒所有的方法效率低。
 10     jdk1.5以后,可以在一个lock锁上加上多个监视器对象。
 11  */
 12 
 13 //1、描述资源
 14 class Resource{
 15     private String name;
 16     private int count=1;
 17     //定义一个锁对象
 18     private Lock lock=new ReentrantLock();
 19     //获取锁上的Condition对象,为了解决本方唤醒对象问题,可以一个锁创建两个监视器对象。
 20     private Condition consu=lock.newCondition();//获取lock上的监视器方法,负责消费
 21     private Condition produ=lock.newCondition();//获取lock上的监视器方法,负责生产
 22     
 23     //定义标记
 24     private boolean flag=false;
 25 
 26     public void set(String name) {//t1,t2
 27         //获取锁
 28         lock.lock();
 29         try {
 30             while(flag) {
 31                 try {produ.await();} catch (InterruptedException e) {}
 32             }
 33             //给成员变量赋值并加上编号
 34             this.name = name+count;
 35             //编号自增
 36             count++;
 37             //打印生产了哪个商品
 38             System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
 39             flag=true;
 40             //唤醒消费者
 41             consu.signal();
 42         }finally {
 43             lock.unlock();//一定要执行
 44         }
 45 
 46     }
 47 
 48     public void out() {//t3,t4
 49         //获取锁
 50         lock.lock();
 51         try {
 52             while(!flag) {
 53                 try {consu.await();} catch (InterruptedException e) {}
 54             }
 55             System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name);
 56             flag=false;
 57             //唤醒生产者
 58             produ.signal();
 59         }finally{
 60             lock.unlock();//一定要执行
 61         }
 62     }
 63 }
 64 
 65 //2、描述生产者
 66 class Producer implements Runnable{
 67     private Resource r;
 68     public Producer(Resource r){
 69         this.r=r;
 70     }
 71     @Override
 72     public void run() {
 73         while(true) {
 74             r.set("面包");        
 75         }
 76     }    
 77 }
 78 //3、描述消费者
 79 class Consumer implements Runnable{
 80     private Resource r;
 81     public Consumer(Resource r){
 82         this.r=r;
 83     }
 84     @Override
 85     public void run() {
 86         while(true) {
 87             r.out();
 88         }
 89     }    
 90 }
 91 
 92 public class ThreadMain4 {
 93     public static void main(String[] args) {
 94         //1、创建资源对象
 95         Resource r=new Resource();
 96         //2、创建线程任务
 97         Producer p=new Producer(r);
 98         Consumer c=new Consumer(r);
 99         //3、创建线程对象
100         Thread t1=new Thread(p);
101         Thread t2=new Thread(p);
102         Thread t3=new Thread(c);
103         Thread t4=new Thread(c);
104 
105         t1.start();
106         t2.start();
107         t3.start();
108         t4.start();
109     }
110 }
ThreadMain4

 

posted @ 2018-10-04 10:18  今夕何希  阅读(1418)  评论(0编辑  收藏  举报