一生产者多消费者模型 / 多生产者一消费者模型

1. 异常问题(wait问题)

wait中断:在调用处立即中断并且释放锁,该线程转入等待队列。当收到持有同一把锁的线程notify通知后进入锁池队列,再次获得同步锁之后才会继续执行。这里的继续执行是从上次中断处继续向下执行

package 多线程核心.生产者与消费者问题.一生产多消费操作栈假死与异常;
import java.util.ArrayList;

class MyService{
    private ArrayList list = new ArrayList();

    synchronized public void push() throws InterruptedException {
        if(list.size() == 1){		//改为while即可解决异常问题
            System.out.println(Thread.currentThread().getName()+" -> wait");
            this.wait();
        }
        System.out.println(Thread.currentThread().getName()+" -> 生产");
        list.add(Math.random());
        System.out.println("size = " + list.size());
        this.notify();
    }

    synchronized public void pop() throws InterruptedException {
        if(list.size() == 0){		//改为while即可解决异常问题
            System.out.println(Thread.currentThread().getName()+" -> wait");
            this.wait();
        }
        System.out.println(Thread.currentThread().getName()+" -> 消费");
        list.remove(0);
        System.out.println("size = " + list.size());
        this.notify();
    }
}

class P{

    private MyService myService;

    public P(MyService myService) {
        this.myService = myService;
    }

    public void pushService(){
        try{
            myService.push();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class C{

    private MyService myService;

    public C(MyService myService) {
        this.myService = myService;
    }

    public void popService(){
        try{
            myService.pop();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class ThreadP extends Thread{
    private P p;

    public ThreadP(P p, String name) {
        super(name);
        this.p = p;
    }

    @Override
    public void run() {
        while(true)
            p.pushService();
    }
}
class ThreadC extends Thread{
    private C c;

    public ThreadC(C c, String name) {
        super(name);
        this.c = c;
    }

    @Override
    public void run() {
        while(true)
            c.popService();
    }
}

public class Demo {
    public static void main(String[] args) {
        MyService myService = new MyService();
        P p1 = new P(myService);

        C r1 = new C(myService);
        C r2 = new C(myService);
        C r3 = new C(myService);
        C r4 = new C(myService);
        C r5 = new C(myService);

        ThreadP p = new ThreadP(p1,"生产者");
        p.start();

        ThreadC c1 = new ThreadC(r1,"消费者-1");
        ThreadC c2 = new ThreadC(r2,"消费者-2");
        ThreadC c3 = new ThreadC(r3,"消费者-3");
        ThreadC c4 = new ThreadC(r4,"消费者-4");
        ThreadC c5 = new ThreadC(r5,"消费者-5");
        c1.start();
        c2.start();
        c3.start();
        c4.start();
        c5.start();

        try{
            Thread.sleep(5000);
            Thread[] arr = new Thread[Thread.currentThread().getThreadGroup().activeCount()];
            Thread.currentThread().getThreadGroup().enumerate(arr);

            for(int i = 0;i < arr.length;i++){
                System.out.println(arr[i].getName() + " -> " + arr[i].getState());
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

  1. 分析:出现异常是因为ArratList删除元素越界了,首先要了解wait中断机制。wait中断是在此处立即中断并且释放锁,转入等待队列。当收到notify通知后进入锁池队列,再次获得同步锁之后才会继续执行。这里的继续执行是从上次中断处继续向下执行。当多个线程都wait之后,突然有个wait线程中断恢复继续向下执行,由于并没有什么条件判断或者限制导致size = 0时依旧将第1个(下标为0)的元素移除,此时ArrayList中已经为空,所以导致数组越界。

  2. 由于中断恢复回来之后,中断前的状态 和 中断恢复后的状态是无法预测的,谁也不知道恢复后是个什么样子,中断时size=0才中断,中断恢复后并不知道此时size是否依旧为0? 所以在恢复后依旧需要继续判断最初的中断条件是否成立。

  3. 解决方法:将if 改为 while。这样即使回来之后size = 0依旧会进行一次判断,如果size == 0继续中断,不为0才能移除ArrayList中的元素。

2. 假死问题(notify问题)

notify唤醒:随机唤醒一个持有同一把锁的wait线程,这个线程是随机无法指定的。

notifyAll唤醒:唤醒所有持有同一把锁的wait线程。

将上面代码中的if 改为 while后依旧存在问题,这里存在 “假死”。

import java.util.ArrayList;

class MyService{
    private ArrayList list = new ArrayList();

    synchronized public void push() throws InterruptedException {
        while(list.size() == 1){
            System.out.println(Thread.currentThread().getName()+" -> wait");
            this.wait();
        }
        System.out.println(Thread.currentThread().getName()+" -> 生产");
        list.add(Math.random());
        System.out.println("size = " + list.size());
        this.notify();			//改为notifyAll();   解决假死
    }

    synchronized public void pop() throws InterruptedException {
        while(list.size() == 0){
            System.out.println(Thread.currentThread().getName()+" -> wait");
            this.wait();
        }
        System.out.println(Thread.currentThread().getName()+" -> 消费");
        list.remove(0);
        System.out.println("size = " + list.size());
        this.notify();			//改为notifyAll();   解决假死
    }
}

class P{

    private MyService myService;

    public P(MyService myService) {
        this.myService = myService;
    }

    public void pushService(){
        try{
            myService.push();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class C{

    private MyService myService;

    public C(MyService myService) {
        this.myService = myService;
    }

    public void popService(){
        try{
            myService.pop();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class ThreadP extends Thread{
    private P p;

    public ThreadP(P p, String name) {
        super(name);
        this.p = p;
    }

    @Override
    public void run() {
        while(true)
            p.pushService();
    }
}
class ThreadC extends Thread{
    private C c;

    public ThreadC(C c, String name) {
        super(name);
        this.c = c;
    }

    @Override
    public void run() {
        while(true)
            c.popService();
    }
}

public class Demo {
    public static void main(String[] args) {
        MyService myService = new MyService();
        P p1 = new P(myService);

        C r1 = new C(myService);
        C r2 = new C(myService);
        C r3 = new C(myService);
        C r4 = new C(myService);
        C r5 = new C(myService);

        ThreadP p = new ThreadP(p1,"生产者");
        p.start();

        ThreadC c1 = new ThreadC(r1,"消费者-1");
        ThreadC c2 = new ThreadC(r2,"消费者-2");
        ThreadC c3 = new ThreadC(r3,"消费者-3");
        ThreadC c4 = new ThreadC(r4,"消费者-4");
        ThreadC c5 = new ThreadC(r5,"消费者-5");
        c1.start();
        c2.start();
        c3.start();
        c4.start();
        c5.start();

        try{
            Thread.sleep(5000);
            Thread[] arr = new Thread[Thread.currentThread().getThreadGroup().activeCount()];
            Thread.currentThread().getThreadGroup().enumerate(arr);

            for(int i = 0;i < arr.length;i++){
                System.out.println(arr[i].getName() + " -> " + arr[i].getState());
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

1. 假死原因分析
  1. 生产者 首先获得锁生产一个产品,notify指令失效(没有同锁wait线程)

  2. 生产者再次快速获得锁由于生产的产品还未消耗 生产者进入wait状态

  3. 此时消费者2获得锁将产品进行了消费(注意:这里消费者1已经诞生但是并没有获得锁,在锁池队列等待),notify唤醒唯一等待的生产者线程

  4. 消费者2继续获得锁,此时已经没有产品消费了, 消费者2进入wait状态

  5. 消费者3拿到锁,此时已经没有产品消费了, 消费者3进入wait状态

  6. 生产者 获得锁生产一个产品,现在等待的有消费者2 消费者3,notify唤醒哪一个是不确定的。根据图片继续分析才知道

  7. 生产者 再次获得锁,产品没有消耗, 生产者进入wait状态

  8. 消费者2获得锁,进行了消费( 这里说明第5步生产者唤醒了消费者2),此时wait的线程有,消费者3和生产者,notify唤醒哪个不知道继续向下分析

  9. 消费者2再次获锁,没有产品消耗, 消费者2进入wait状态

  10. 消费者3获锁(这里说明第7步 消费者2唤醒了消费者3),没有产品消耗, 消费者3进入wait状态

  11. 消费者4获锁,没有产品消耗, 消费者4进入wait状态

  12. 此时沉默许久的消费者1首次获得锁, 没有产品消耗, 消费者1进入wait状态

  13. 消费者5获锁,没有产品消耗, 消费者5进入wait状态

  14. 到此生产者在第6步wait之后再也没起来过,后续由于第7步的消费者2错误唤醒了同样是消费者的线程3导致一蹶不振,至此线程全线wait导致假死。

假死的原因根据上面的分析很容易得出,由于notify随机唤醒,连续唤醒同类(同样是消费者)导致假死

2. 假死解决方案

所有的notify改为nofityAll(),这样让每个线程都将所有的线程全部唤醒,这样就解决了随机唤醒并且唤醒同类的问题。因为总能保证生产者一直处于锁池队列争抢同步锁进入Runnable状态。



一生产多消费模型 和 多生产一消费模型是一样的,一样需要注意异常和假死问题。

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值