class Resource{
private String name;
private int count;
private boolean flag = false;
public synchronized void set(String name){
while(flag){//将if语句变为while语句,使得线程被唤醒后,能够再次进行flag的判断
try{
this.wait();
}
catch(InterruptedException e){
}
}
this.name = name + count ;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
notifyAll();//仅仅是notify(),可能会唤醒同一个锁上的线程,从而出现死锁现象
}
public synchronized void out(){
while(!flag){//将if语句变为while语句,使得线程被唤醒后,能够再次进行flag的判断
try{
this.wait();
}
catch(InterruptedException e){
}
}
System.out.println(Thread.currentThread().getName()+".......消费者.."+this.name);
flag = false;
notifyAll();//仅仅是notify(),可能会唤醒同一个锁上的线程,从而出现死锁现象
}
}
class Producer implements Runnable{
private Resource r;
Producer(Resource r){
this.r = r;
}
public void run(){
while(true){
r.set("烤鸭");
}
}
}
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this.r = r;
}
public void run(){
while(true){
r.out();
}
}
}
class ProConTest{
public static void main(String [] args){
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);//多生产者,多消费者
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
/*
多生产者多消费者可能出现的问题:
1.生产了,但是没有被消费:(多生产)
Thread-0...生产者...烤鸭697
Thread-1...生产者...烤鸭698
Thread-2.......消费者..烤鸭698
Thread-0...生产者...烤鸭699
Thread-1...生产者...烤鸭700
Thread-2.......消费者..烤鸭700
2.生产一个,被多次消费:(多消费)
Thread-1...生产者...烤鸭387
Thread-3.......消费者..烤鸭387
Thread-2.......消费者..烤鸭387
Thread-1...生产者...烤鸭388
Thread-3.......消费者..烤鸭388
Thread-2.......消费者..烤鸭388
*/
}
}
多生产多消费问题解析:
先从生产烤鸭一开始,把代码走一遍:
生产了烤鸭一,flag 变为true,t0线程等待,t0一挂,此时活着的线程有三个,这三个处于临时阻塞状态,并且都有可能被CPU执行到,假设t1被执行到了,flag为true,t1也挂了。
接着,假设t2抢到了执行权,消费了烤鸭一,flag变为false,这时候notify(),此时线程池里有t0和t1,假设t0在这个时候活了,但此时是没有执行权的,此时t2继续执行,!flag为真,t2挂了。现在是t0和t3活着。
假设t3抢到执行权了,!flag为真,t3也挂了。 此时只有t0活着。
现在关键来了:
if(flag){
try{this.wait()}catch(InterruptedException e){}
}
t0活了以后,它还判判断标记么?
if语句判断一次就结束了,所以t0会继续往下走,生产烤鸭二
this.name = name + count ;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag变为true;
关键来了,此时notify();t1、2、3都有机会被唤醒,假设t1被唤醒了,flag为真,t0挂了,这时候t1执行,t1不判断标记,往下走,烤鸭三就出来了
所以,会出现多生产的问题,多消费的问题也是类似:
t0、1、2都挂了,t3消费一次后,唤醒的不是t0、1,而是t2,就出现了多消费的情况。
解决方案:
我们希望,线程被唤醒后,能够进行多次判断,能够进行多次判断的是,while
所以,将if(flag)修改为while(flag)
但这个时候又出现了死锁问题:
H:\JavaCode>java ProConTest
Thread-0...生产者...烤鸭0
Thread-3.......消费者..烤鸭0
Thread-1...生产者...烤鸭1
Thread-3.......消费者..烤鸭1
Thread-0...生产者...烤鸭2
H:\JavaCode>java ProConTest
Thread-0...生产者...烤鸭0
Thread-3.......消费者..烤鸭0
Thread-1...生产者...烤鸭1
Thread-3.......消费者..烤鸭1
Thread-0...生产者...烤鸭2
H:\JavaCode>java ProConTest
Thread-0...生产者...烤鸭0
Thread-3.......消费者..烤鸭0
Thread-1...生产者...烤鸭1
Thread-3.......消费者..烤鸭1
Thread-0...生产者...烤鸭2
死锁问题分析:
假设现在的线程状态是,t0生产完了,t1、2、3都挂掉了,t0生产完后,flag变为true,notify();如果唤醒的是同锁的线程t1,因为while的存在,t0进行判断,挂了,t1进行判断,也挂了,这个时候,4个线程都挂掉了,就出现了死锁的状态。
死锁问题解决方案:
现在我们都知道,如果唤醒的是t2、3这两个线程中的任意一个, 就不会出现这样的问题,那么,我们可不可以指定唤醒某线程,遗憾的是,jdk1.5之前并没有提供这样的方法。
退而求其次,我们将所有的线程都唤醒notifyAll();,这样即使t1也挂了,仍然还有其它线程的存在。
小结:
生产者,消费者
多生产者,多消费者的问题
if判断标记,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况
while判断标记,解决了线程获取执行权后,是否要运行的问题
notify:只能唤醒一个线程,如果本方唤醒本方,没有意义,而且while判断标记+notify()会导致线程死锁。
notifyAll解决了本方线程一定会唤醒对方线程的问题。