• (三)(2)wait/notify实现生产者-消费者模型,join方法


    生产者,消费者模型

    举个例子来说明,厨师,服务员,厨师做菜,服务员上菜,如果厨师没有做好菜,那么服务员就无法上菜,厨师做好了菜,然后通知服务员消费(上菜)。在这个过程之中,厨师扮演的就是生产者,服务员扮演消费者。

    一句话说:生产者没有生产出来东西,消费者就必须等待着,生产者生产出来了,就通知消费者进行消费。

    很明显,消费者等待就对应我们的wait方法,生产者通知消费者对应notify方法,所以,生产者,消费者模型可以用notify,wait来实现。

    那么与之对应的,生产者不可能只有一个,消费者也不可能只有一个,所以,他们之间就有了一对一,一对多,多对一,多对多关系。

    一对一模型:

    如下就是一个简单的一个生产者,一个消费者的模型:

    MyList.java,用于存储数据

    View Code

    Produce.java:生产者

    package 第三章_wait_join;
    
    //生产者
    public class Produce {
        private String lock;
        public Produce(String lock){
            this.lock=lock;
        }
        public void setValue(){
            try{
                synchronized (lock){
                    if(MyList.getSize()!=0){   //无需生产
                        lock.wait();
                    }
                    //生产
                    String temp = "设置了值"+System.currentTimeMillis();
                    System.out.println(temp);
                    MyList.list.add(temp);
                    lock.notify();  //通知消费者
                }
    
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    View Code

    Reduce.java,消费者

    package 第三章_wait_join;
    
    //消费者
    public class Reduce {
        private String lock;
        public Reduce(String lock){
            this.lock=lock;
        }
        public void getValue(){
            try{
                synchronized (lock){
                    if(MyList.getSize()==0){   //等待生产
                        lock.wait();
                    }
                    //消费
                    System.out.println("获取到的值是:"+MyList.list.remove(0));
                    lock.notify();  //通知消费者
                }
    
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    View Code

    ThreadP生产线程:

    package 第三章_wait_join;
    
    public class ThreadP extends Thread{
        private Produce produce;
        private Produce p;
        public ThreadP(Produce p){
            this.produce=p;
        }
        public void run(){
            while(true){
                produce.setValue();
            }
        }
    }
    View Code

    ThreadR 消费线程:

    package 第三章_wait_join;
    
    //消费者
    public class Reduce {
        private String lock;
        public Reduce(String lock){
            this.lock=lock;
        }
        public void getValue(){
            try{
                synchronized (lock){
                    if(MyList.getSize()==0){   //等待生产
                        lock.wait();
                    }
                    //消费
                    System.out.println("消费者"+Thread.currentThread().getName()+"获取到的值是:"+MyList.list.remove(0));
                    MyList.list.remove(0);
                    lock.notify();  //通知消费者
                }
    
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    View Code

    test.java.

    package 第三章_wait_join;
    
    import java.sql.ResultSet;
    
    public class test {
        public static void main(String[] args){
            try {
                String lock = "lock";
                Produce p = new Produce(lock);
                Reduce r = new Reduce(lock);
                ThreadP threadP = new ThreadP(p);
                ThreadR threadR = new ThreadR(r);
                threadP.start();
                threadR.start();
                ThreadR.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    View Code

    运行结果:

    可以看到,符合预期,生产一个,消费一个,轮换进行。

    多生产,多消费情况:

    问题:这种模式如果按照我们上面的写法,会出现一个问题,就是可能现在既有生产者,也有消费者在等待,这时候如果某一方发出了通知,比如生产者生产好了,发出通知,按道理是应该唤醒一个消费者的,但是也有可能唤醒其他生产者,而不是消费者,那这样很明显是不合理的,如果这样的次数多了,就会导致所有生产者和消费者都处于wait状态,那么这个时候程序就会出现假死状态。比如下将test.java更改为下面的代码,运行之后会发现程序运行一会就处于”假死”状态了。

    package 第三章_wait_join;
    
    import java.sql.ResultSet;
    
    public class test {
        public static void main(String[] args){
            try {
                String lock = "lock";
                Produce p = new Produce(lock);
                Reduce r = new Reduce(lock);
                ThreadP[] threadP = new ThreadP[2];
                ThreadR[] threadR = new ThreadR[2];
                for(int i=0;i<2;i++){
                    threadP[i] = new ThreadP(p);
                    threadP[i].setName("生产者:"+i);
                    threadR[i] = new ThreadR(r);
                    threadR[i].setName("消费者:"+i);
                    threadP[i].start();
                    threadR[i].start();
                }
                ThreadR.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    View Code

    解决办法:

    使用notifyAll()  替换 notify每次将所有线程都唤醒,包括自己的异类,这样就能保证生产的都被消费,消费者都有所消费,不会出现上述状况。

    一个生产者,多消费:

    问题:这种情况下,会出现两个问题。

    1.条件改变时,其他消费者没有及时得到响应,以为可以消费,继续执行下去,这个时候如果是一个数组之类的,就会出现越界的异常,,,,这个没太看懂。

    2.和多生产多消费一样,消费者唤醒时候可能唤醒同类,同类没有可以消费的,继续等待,下一个同类也没有可以消费的,继续等待,最后导致全部同类等待,生产者也等待,没有能够notify的,自然假死,

    解决:

    1.将判断条件是否成立的语句改为while循环进行判断

    2.notifyAll()唤醒所有线程

    多生产,一个消费:

    这种一般没有什么问题,不会假死,因为就算生产者唤醒了生产者,然后发现还没有被消费,又等待,以此类推,所有生产者都等待了,但是最后总会唤醒消费者,消费者总能消费,消费之后总能唤醒一个生产者进行生产。

    Join:

    这个方法是调用该方法的线程执行完毕之后,当前线程再执行接下来的语句,有下面几个特点:

    1.会释放锁,换到另一个线程执行,相比之下sleep并不会释放锁。原因是join内部是使用wait实现的,wait会释放锁

    2.和wait一样,已经处于阻塞状态了,就不能再用其他方法让该线程处于阻塞状态

    3.join(long)   long时间之后,如果目标线程还没有执行完,则取消阻塞状态,进入就绪状态

    4.同时使用sleep和join(long)可能会出现join后面的代码提前之前,原因是可能join(long)在竞争锁的过程之中,没有竞争过其他线程,其他线程之中使用了slee(long2)long2 > long,这就意味着join已经过了有效时间,所以不再阻塞,而是和调用join的线程竞争,竞争自然可能有很多结果了

    。。不放代码了,麻烦

      

  • 相关阅读:
    TCP协议的三次握手、四次挥手
    .NET Framework 3.5 安装
    grep命令总结
    线性回归
    K-Mean聚类算法
    Logistic回归
    朴素贝叶斯
    Decision Tree
    KNN
    GCC for Win32开发环境介绍
  • 原文地址:https://www.cnblogs.com/eenio/p/11385090.html
Copyright © 2020-2023  润新知