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


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

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

    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
  • 相关阅读:
    [Unity3D]计时器/Timer
    unity缓存和浏览器缓存
    unity3d进行脚本资源打包加载
    Unity3d删除无用的美术资源
    项目经理的职责(转载)
    LINQ
    生意经
    Android ListView标题置顶效果实现
    ListView的自动循环滚动显示
    郭霖的专栏
  • 原文地址:https://www.cnblogs.com/hopeyes/p/9741242.html
Copyright © 2020-2023  润新知