public class Test_Producer_Consumer {
public static void main(String[] args) {
Resource res = new Resource();
Producer pro = new Producer(res);
Consumer con = new Consumer(res);
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 Resource {
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name) {
while(flag) { //if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + "..." + count++;
System.out.println(Thread.currentThread().getName() + "...生产了..."
+ name);
flag = true;
this.notifyAll(); //this.notify();
}
public synchronized void out() {
while(!flag) { //if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()
+ "...消费了.............." + name);
flag = false;
this.notifyAll(); //this.notify();
}
}
class Producer implements Runnable {
private Resource res;
Producer(Resource res) {
this.res = res;
}
@Override
public void run() {
while (true) {
res.set("商品");
}
}
}
class Consumer implements Runnable {
private Resource res;
Consumer(Resource res) {
this.res = res;
}
@Override
public void run() {
while (true) {
res.out();
}
}
}
生产者消费者比较容易出错的地方就是被注释的地方,如果把注释的地方那行代码换成注释中的代码就会出现多次生产少次消费,或者少次生产多次消费的现象,或者出现所有线程都睡过去的现象,当然产生的效果也因电脑而异,下面让我们一起分析下产生这种问题的原因:
从main方法开始,main方法启动线程t1、t2、t3、t4,可能是以下都启动了,也可能不是,这并不重要,接下来比如t1先执行了,t1执行res的set(String name)方法,执行的时候t1就锁住了this对象,也就是把res锁住了,别的线程访问不了res对象了,t1先判断flag为真,不进入wait,然后对res对象的name进行赋值,输出生产者的那句话,将flag赋值为true,接下来唤醒线程池中的第一个等待的线程,这个时候线程池中没有第一个等待的线程,谁也不唤醒,接下来t1执行完了,释放锁,但是有可能t1又执行了,又拿到了锁,这个时候t1判断flag为真,就睡过去了,这个时候要注意了,t1就失去了对锁的控制!然后t2开始执行,t2执行的时候也锁上了资源,判断flag为真,也睡过去了,失去了对锁的控制,然后t3开始执行,锁上资源后,判断flag是真,不执行wait,输出消费者的那句话,将flag赋值为false,然后执行notify,唤醒第一个在线城池中等待的线程,也就是t1,然后t3再次执行,判断flag是假就睡过去了,然后t4执行,t4判断flag也是假,也睡过去了,这个时候只有t1是醒的,t1醒来后先找到锁,锁上res对象,也不用做判断语句了,直接执行,然后对res对象的name进行赋值,输出生产者的那句话,将flag赋值为true,接下来就很重要了!t1执行notify唤醒线程池中第一个等待的线程,t1唤醒的是t2!t1接着执行t1判断flag是真,就wait了,t2醒来后先找到锁,锁上res对象,然后也不用判断flag了!直接执行下面的语句,这样就出错了,本来t2不应该执行才对的。就这样自己推就可以把所有出现问题的原因推理出来。
出现问题有两个原因:
1.t2醒来后没判断flag
2.notify方法只能唤醒线程池中的第一个wait的线程
所以解决方法是:
1.循环判断
2.notifyAll唤醒所有wait的线程