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多线程编程核心技术》