生产者和消费者引出的多线程通信

16 篇文章 0 订阅
4 篇文章 0 订阅

现象

          当开启了多个线程后,出现了生产了一个产品,消费两个产品的情况,也有可能出现生产两个,消费一个的情况。如下图所示:

                  

分析


         假设生产者先获取到了CPU的执行权,生产者有两个线程t1,t2,消费者有两个t3,t4,t1遇到flag(为假),即里面没有数值不用等待,直接生产(即打印),之后将flag改为true,notify之后循环到if判断,flag变为true,此时t1放弃资格,进入等待
         此时t2,t3,t4都有可能抢夺到CUP资源,假设t2此时抢到了资源,判断其为true,也进行等待!此时t3抢到了执行权,判断条件
        执行完之后(消费完),执行到flag = false,再 进行唤醒,flag变成了false,此时唤醒了t1,t3放弃了资格,t4读到之后也会变成等待,之后t1又继续执行,即再次生产了一次,,此时t1又再次将flag标记为true,唤醒了t2,此时, t1又再次进行判断,因为flag为true,所以只能等待,此时t2不能判断flag,直接又进入生产,就出现了就出现了生产多次的情况
        
        注:此例中生产者默然flag = false时生产,消费者flag = true时消费
        
原因

       是因为多个线程线程产生之后(t1,t2,t3,t4),t1判断flag输入值之后,将t2唤醒,但是t2无法判断if(flag),就会直接
       执行以下的代码,即继续生产(t4无法判断if(!flag),也会直接执行消费的动作),即将if条件判断改成while(flag)循环,这样无论是t2还是t4,遇到while都会去判断flag.
               但是用了while循环以后,又会出现一种情况就是所有的线程都会等待,t1把自己同步代码块中的线程(t2)唤 醒了,但是无法唤醒 其他同步中的线程(t3,t4), 所以将notify()方法换成notifyAll(),就会唤醒全部的线程,再有了while循环,线程就会自己
      判断是否应该执行
     
总结

    当程序中只有一个生产者,和一个消费者时,同步代码块中使用的判断和唤醒方式是if(flag),和notify();—>即单一线程
      
       防止出现生产一个,消费多个的办法:  
              当程序中有多个生产者和消费者时,同步代码块中使用的判断和唤醒方式是while(flag),和notifyAll();—>即多  个线程
      
代码示例:

package com.itheima.day12;        

public  class ProducerConsumerDemo {

    public static void main(String[] args) {
        ResourcePC r = new ResourcePC();
        Producer pro = new Producer(r);
        Consumer con = new Consumer(r);
        
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(pro);
        Thread t3 = new Thread(con);
        Thread t4 = new Thread(con);
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }

}
  
//创建资源类
class ResourcePC{   
    private String name;

     //定义一个计数器
    private int count = 1;
    private boolean flag = false;
 
//涉及到安全问题用同步synchronized修饰函数,即用同步函数
  public  synchronized void set(String name){  
       while(flag)
        try{
            this.wait();
            
        }catch(Exception e){
            
        }
    this.name = name+" "+count++;
    System.out.println(Thread.currentThread().getName()+"....生产者...."+this.name);
    flag = true;
    //this.notify();
    this.notifyAll();
  }

//定义打印方法
  public synchronized void PrintOut(){
      while(!flag)
        try{this.wait();}catch(Exception e){} //注意实际开发中不允许这样写,此处是为了对比
    System.out.println(Thread.currentThread().getName()+"..消费者.."+this.name);
    flag = false;
    //this.notify();
    this.notifyAll();
   }

}

class Producer implements Runnable{
    private ResourcePC res;
    Producer(ResourcePC res){
        this.res = res;
    }
    public void run(){
        while(true){
            res.set("+商品+");
        }
    }
}

class Consumer implements Runnable{
    private ResourcePC res;
    Consumer(ResourcePC res){
        this.res = res;
    }
    public void run (){
        while(true){
            res.PrintOut();
        }
     }
  }

线程间通信:

     新的需求: 

                用notifyAll()方法唤醒所有的线程之后,现在只要求唤醒对方的线程(消费者中的线程t3,t4),
        不用唤醒自身中的线程(生产者中的t2线程)

                在Java的API文档,有一个java.util.concurrent.locks的包中有一个Lock接口 ,其中描述道:
    “Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作,此实现允许更灵活的结
     构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象”。

   由此可见:
      Lock替代了synchronized,
      Condition替代了Object类中的监视器方法,wait(),notify()notifyAll(),
      对应的变成了await(),signal()signalAll()


  Condition的由来 :
               把wait(),notify()封装成Condition对象,Condition应该怎么来呢,如何获取?要通过锁来获取
      因为wait(),notify()本应定义在同步代码(synchronized)块当中,同步中有锁,每一个wait(),
      notify(),都要标识自己所属的锁,现在同步变成了Lock,而wait,notify变成了Condition
     
      Lock接口当中就定义了newCondition();的方法
      则:
          Condition cons = lock.newCondition();
 
 分析
               在lock.lock()之后   ,如果拿到锁了,在执行被锁代码中condition.await();抛了异常,之后
     功能会结束  ,lock.unlock();没有执行到,即所没有被释放,其他线程无法进来功能,而lock.unlock();
     是一定要背执行的,此时就可以用try-finally  
    
     用Lock和同步 synchronized会达到同样的效果,此时还没有达到新的需求的效果,即就是只唤醒对方的线程(t3,t4),
       不用唤醒自己当中的其他线程(t2)
         此时,就会用到Lock具备的新的特性:
              一个锁上可以拥有(支持)多个相关的Condition对象
            private Condition condition_pro = lock.newCondition();
             private Condition condition_con = lock.newCondition();
          
             condition_pro.signal();//唤醒生产者(t1,t2线程),此时它只唤醒t1或者t2中的一个,
    原理:      
            生产者代码里con唤醒消费者中的线程(t3,t4),消费者功能代码中 pro唤醒生产者(t1,t2),于是就实现了唤醒
                 对方的线程,而不唤醒自己线程的需求

等待唤醒机制示意图:

             

代码示例:

package com.itheima.day12;
import java.util.concurrent.locks.Condition; 
 public class ProducerConsumerDemo2 {
         public static void main(String[] args) {
             ResourcePC2 r = new ResourcePC2();
        
                 Producer2 pro = new Producer2(r);
                 Consumer2 con = new Consumer2(r);
        
                 Thread t1 = new Thread(pro);
                 Thread t2 = new Thread(pro);
                 Thread t3 = new Thread(con);
                 Thread t4 = new Thread(con);
        
                 t1.start();
                 t2.start();
                 t3.start();
                 t4.start();
    }

}

class ResourcePC2{   
    private String name;
    private int count = 1;
    private boolean flag = false;
    
    //ReentrantLock是Lock的一个子类,也叫实现类,它具有自己的构造函数
    private Lock lock = new ReentrantLock();
    
    //通过锁产生了一个具备wait,notify功能的实例对象
    //private Condition condition = lock.newCondition();//Condition对象的由来
    
    //使用Lock的新特性:一个锁上可以拥有(支持)多个相关的Condition对象,升级版的为:
     private Condition condition_pro = lock.newCondition(); //生产者
     private Condition condition_con = lock.newCondition(); //消费者
    
 
    public void set(String name) throwsInterruptedException {  
        //获取锁
      lock.lock();
      
      try {

               //判断标记后若为true就需要等待:

              while(flag) 
              //condition.await();  //抛了异常,要try一下 。(未用Lock新特性之前)
            
                condition_pro.await();     //若生产者(t1,t2线程)生产完之后,等待,唤醒消费者(用新特性)
              this.name = name+" "+count++;
              
              System.out.println(Thread.currentThread().getName()+"....生产者...."+this.name);
              flag = true;
              //condition.signal();  //唤醒 (未使用Lock新特性)
             // conditio.signalAll(); //唤醒所有线程

              
              condition_con.signal();//唤醒消费者(t3,t4线程)
              
          }finally{   
              
              lock.unlock();//释放锁
          }
    }

    public  void PrintOut() throws InterruptedException{  
      
      lock.lock();
      try {
           while(!flag)
               //condition.await();    
               
               condition_con.await();
           System.out.println(Thread.currentThread().getName()+"..消费者.."+this.name);
           flag = false;
                
            //condition.signal();//只是唤醒了部分线程,运行会出现等待,卡死的现象,所以将其换成signalAll();
            //condition.signalAll();


              //唤醒生产者(t1,t2线程),此时它只唤醒t1或者t2中的一个
              condition_pro.signal();
           
          }finally{

             
              lock.unlock();
          }
    }
}

class Producer2 implementsRunnable{
    private ResourcePC2 res;
    Producer2(ResourcePC2 res){
        this.res = res;
    }
    public void run(){
        while(true){
            try {
                res.set("+商品+");
                
               } catch (InterruptedException e) {
                
                       }
        }
    }
}

class Consumer2 implements Runnable{
    private ResourcePC2 res;
    Consumer2(ResourcePC2 res){
        this.res = res;
    }
    public void run (){
        while(true){
            try {
                   res.PrintOut();
                   
                } catch (InterruptedException e) {
                
                        }
            }
        
         }
    
   }

总结
        JDK1.5之后,就提供了多线程升级 的解决方案:(提供了显示的锁机制以及显示的锁对象上的等待唤醒操作制)
                                将同步synchronized替换成显示的Lock操作
                                将Object中的wait,notify,notifyAll,封装成了Condition对象
                                该对象可以Lock锁,进行获取
         该示例中,实行了本方只唤醒对方线程的操作,以前创建一个锁,就需要进行同步,嵌套之后容易形成死  锁, 有了Lock以后,一个锁可以拥有多组(wait,notify)                                                        



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值