多线程之生产者/消费者模式(值操作)

本文介绍了在Java中,一个生产者和一个消费者模型如何正常工作,展示了如何扩展到多生产者、多消费者模式,以及在此模式下可能出现的假死现象。通过分析假死原因,作者指出使用`notifyAll()`而非`notify()`可以避免此类问题,确保程序正常运行。

1、一个生产者,一个消费者(值操作)
本小节是典型的单个生产者,单个消费者,生产对象为值的Demo
如下,我们假设ValueObject .value为生产的值,当value的值为 “”(空串)时,我们认为生产出来的对象已被消费,当value的值不为空串时,我们认为生产出来的对象未被消费。

public class ValueObject {
    public static String value = "";
}

生产者

public class P {
    private String lock;
    public P(String lock) {
        super();
        this.lock = lock;
    }

    public void setValue() {
        try {
            synchronized(lock) {
                while(!ValueObject.value.equals("")) {
                    //当value的值不为 ""(空串)时,可以认为生产出来的东西未被消费,所以生产者进入等待状态,直到消费者将其消费再继续生产。
                    lock.wait();
                }               
                String value = System.currentTimeMillis() + "" + System.nanoTime();
                System.out.println("set的值是" + value);
                ValueObject.value = value;
                lock.notify();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

消费者

public class C {
    private String lock;
    public C(String lock) {
        super();
        this.lock = lock;
    }

    public void getValue() {
        try {
            synchronized (lock) {
                while(ValueObject.value.equals("")) {
                    //当value的值、为 ""(空串)时,可以认为生产出来的东西已经被消费,所以消费者进入等待状态,直到生产者生产完成后再进行消费。
                    lock.wait();
                }
                System.out.println("get的值是" + ValueObject.value);
                ValueObject.value = "";
                lock.notify();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

下面分别附上负责生产和消费两条线程的代码

public class ThreadP extends Thread {
    private P p;
    public ThreadP(P p) {
        super();
        this.p = p;
    }
    public void run() {
        while(true) {
            p.setValue();
        }
    }
}
public class ThreadC extends Thread {
    private C c;
    public ThreadC(C c) {
        super();
        this.c = c;
    }
    public void run() {
        while(true) {
            c.getValue();
        }
    }
}

下面是主函数

public static void main(String[] args) {
    String lock = new String("");
    P p = new P(lock);
    C c = new C(lock);
    ThreadP threadP = new ThreadP(p);
    ThreadC threadC = new ThreadC(c);
    threadP.start();
    threadC.start();
}

运行结果如下:
这里写图片描述

从运行结果我们看到,set和get交替出现,对应了一生产者和一消费者进行数据交互,程序正常运行。

但是,如果在上述模式的基础上扩展成多生产多消费的模式,运行的过程中就极有可能出现假死的情况。

2、多个生产者,多个消费者(值操作)
我们将消费者P,生成者C的代码改成如下:

public class P {
    private String lock;
    public P(String lock) {
        super();
        this.lock = lock;
    }

    public void setValue() {
        try {
            synchronized(lock) {
                while(!ValueObject.value.equals("")) {
                    System.out.println(Thread.currentThread().getName()+ "进入等待");
                    lock.wait();
                }               
                String value = System.currentTimeMillis() + "" + System.nanoTime();
                System.out.println(Thread.currentThread().getName()+ "开启生产");
                ValueObject.value = value;
                lock.notify();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class C {
    private String lock;
    public C(String lock) {
        super();
        this.lock = lock;
    }

    public void getValue() {
        try {
            synchronized (lock) {
                while(ValueObject.value.equals("")) {
                    System.out.println(Thread.currentThread().getName()+ "进入等待");
                    lock.wait();
                }
                System.out.println(Thread.currentThread().getName()+ "开启消费");
                ValueObject.value = "";
                lock.notify();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

主函数下如下:

public static void main(String[] args) {
String lock = new String("");
    P p = new P(lock);
    C c = new C(lock);
    ThreadP[] threadP = new ThreadP[2];
    ThreadC[] threadC = new ThreadC[2];
    for(int i = 0; i < 2; i++) {
        threadP[i] = new ThreadP(p);
        threadP[i].setName("生产者" + (i + 1));
        threadC[i] = new ThreadC(c);
        threadC[i].setName("消费者" + (i + 1));
        threadP[i].start();
        threadC[i].start();
        }
    }

运行结果如下:
这里写图片描述

如图,运行后,程序进入了假死状态,无法正常继续运作。

我们来分析一下出现这种结果的原因:
将运行结果粘贴到下面

1 生产者1开启生产
2 生产者1进入等待
3 消费者2开启消费
4 消费者2进入等待
5 消费者1进入等待
6 生产者2开启生产
7 生产者2进入等待
8 消费者2开启消费
9 消费者2进入等待
10 消费者1进入等待
11 生产者1开启生产
12 生产者1进入等待
13 生产者2进入等待

1)程序开始,生产者1进行生产,生产完成后发出通知(但属于通知过早,并无产生效果)。
2)生产者1生产完成后,再次抢到了lock锁,检测到产品未被消费,进入等待状态。
3)消费者2被start()启动,将产品进行消费并发出通知,唤醒第8行的消费者2。
4)消费后,消费者2再次抢到锁,但发现产品未被生产,所以进入了等待。
5)消费者1被start()启动,发现产品被消费,进入等待状态。
6)生产者2被start()启动,发生产品已被消费,所以进入生产状态,生产完成后唤醒了第10行的消费者1。
7)同2),生产者2生产完成后,再次抢到了lock锁,检测到产品未被消费,进入等待状态。
8)消费者2进入下一次while()循环,消费产品后唤醒第11行的生产者1。
9)消费者2再次抢到锁,发现产品未被生产,进入等待。
10)消费者1进入下一次while()循环,发现产品被消费,进入等待状态。
11)生产者1发现产品已被消耗,进入生产,生产完成后,唤醒第13行的生产者2
12)生产者1抢到锁,发现产品未消费,进入等待状态。
13)生产者2被唤醒,发现产品未消费,进入等待状态。

由上述分析可得出结论,程序出现假死的原因是:生产者1生产完成后发出通知唤醒的是生产者2,而不是消费者。动作完成后通知的是同类,当连续唤醒同类时,就非常容易出现假死的情况。

解决方法:将notify()方法换成notifyAll()方法,将同类异类一同唤醒,这样就不至于出现假死的情况了。





声明:本文的例子全部来自于《Java多线程编程核心技术》

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值