一、通过 java.lang.Object#wait(),java.lang.Object#notify,java.lang.Object#notifyAll来实现 生产者,消费者
public abstract class Goods {protected String type;protected String goodName;protected int number;public abstract void produce();public abstract void consume();}public class Producer implements Runnable{private Goods goods;public Producer(Goods goods){this.goods = goods;}@Overridepublic void run() {while (true){goods.produce();}}}public class Consumer implements Runnable{private Goods goods;public Consumer (Goods goods){this.goods = goods;}@Overridepublic void run() {while (true){goods.consume();}}
}
1.单个生产者,单个消费者
public class OneProducerOneConsumerGoods extends Goods{//false 表示货物是空的 可以继续生产private volatile boolean flag = false;public OneProducerOneConsumerGoods(String type , int beginNumber){this.type = type;this.number = beginNumber;}public synchronized void produce(){String name = Thread.currentThread().getName();//如果不为空 先等待if(flag){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}this.goodName = type + ":" + number;System.out.println(name + " 生产商品 " + goodName);number ++;flag = true;this.notify();}public synchronized void consume(){String name = Thread.currentThread().getName();//如果 flag == false 货物是空的,等待if(!flag){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(name + " 消费商品 " + goodName);flag = false;this.notify();}}
public class OneProducerOneConsumerTest {public static void main(String[] args) {Goods goods = new OneProducerOneConsumerGoods("面包" , 1);Producer producer = new Producer(goods);Consumer consumer = new Consumer(goods);Thread thread = new Thread(producer);Thread thread1 = new Thread(consumer);thread.start();thread1.start();}}这种情况没有出现数据安全问题,也没有出现死锁
OneProducerOneConsumerGoods 是用于单个生产者单个消费者的,这里使用多个生产者和多个消费者,对 OneProducerOneConsumerGoods 进行操作看看会出现什么问题
/*** OneProducerOneConsumerGoods 是用于单个生产者单个消费者的* 这里使用多个生产者和多个消费者,对 OneProducerOneConsumerGoods 进行操作* 看看会出现什么问题*/
public class MultiProducerMultiConsumerTest {public static void main(String[] args) {Goods goods = new OneProducerOneConsumerGoods("面包" , 1);Producer producer = new Producer(goods);Consumer consumer = new Consumer(goods);Thread thread0 = new Thread(producer);Thread thread1 = new Thread(producer);Thread thread2 = new Thread(consumer);Thread thread3 = new Thread(consumer);thread0.start();thread1.start();thread2.start();thread3.start();}}
出现了连续生产或者连续消费的现像,出现这种现像的原因是,当线程调用监视器的wait()方法的时候,不紧会放弃cpu的执行权,处于休眠状态,还会释放掉监视器,这样其他线程就可以进入到synchronized 同步代码块中。
1.解释一下连续生产
如果 Thread-0 判断 flag == false,生产一个,将flag修改为 true,然后cpu继续执行 Thread-0,下次判断 flag == true ,Thread-0 进入 wait() ,释放cpu的执行权 ,Thread-1 执行,判断 flag == true ,Thread-1 进入 wait(),释放cpu的执行权 ,这时Thread-0 Thread-1 都进入wait(),Thread-2 开始执行,判断 flag == true,进行消费,然后将flag 修改为 false,Thread-2 消费完了,会通过notify()唤醒一个线程,这时候不管时Thread-2 继续执行还是 Thread-3 执行都会进入到 wait(),Thread-2 通过notify() 唤醒 Thread-0 Thread-1 中的一个,假如唤醒了 Thread-0 他不会再去判断 flag 而是直接往下执行,去生产,生产完Thread-0 通过notify() 唤醒一个线程,这个时候如果 Thread-1 被唤醒,他也不会再去判断 flag ,而是直接往下执行,进行生产,这样就发生了连续生产,如果碰巧 Thread-0 和 Thread-1 连续的相互唤醒,就会出现长时间的连续生产
2.解释一下连续消费
如果 Thread-2 判断 flag == false 进入wait() ,Thread-3 开始执行,也进入到wait(),这时候Thread-2 Thread-3 都处于wait(),这是Thread-0 开始执行判断flag == false,进行生产 ,将 flag 修改为 true,调用监视器的 notify() 唤醒 Thread-2 Thread-3 中的一个,假如唤醒了Thread-2 ,他不会再去判断 flag 而是直接往下执行去消费,消费完将flag修改为false,调用监视器的 notify() 唤醒一个线程,如果这时正好唤醒了 Thread-3,他不会再判断 flag ,直接往下执行,去消费,这样就发生了连续的消费,如果碰巧 Thread-2 Thread-3 连续的相互唤醒,就会出现长时间的连续消费
3.发生这两种现像的原因是,不管生产者还是消费者,如果他们 wait() 之后被唤醒,不会再判断 flag ,导致在不该生产的时候进行了生产,不该消费的时候进行了消费
这里对OneProducerOneConsumerGoods 进行修改,被唤醒之后再次判断 flag
public class MultiProducerMultiConsumerGoods extends Goods{//false 表示货物是空的 可以继续生产private volatile boolean flag = false;public MultiProducerMultiConsumerGoods(String type , int beginNumber){this.type = type;this.number = beginNumber;}public synchronized void produce(){String name = Thread.currentThread().getName();//这里用while循环,如果wait() 被唤醒后,再次判断 flag//如果不为空 先等待while (flag){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}this.goodName = type + ":" + number;System.out.println(name + " 生产商品 " + goodName);number ++;flag = true;this.notify();}public synchronized void consume(){String name = Thread.currentThread().getName();//这里用while循环,如果wait() 被唤醒后,再次判断 flag//如果 flag == false 货物是空的,等待while (!flag){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(name + " 消费商品 " + goodName);flag = false;this.notify();}}
public class MultiProducerMultiConsumerTest1 {public static void main(String[] args) {Goods goods = new MultiProducerMultiConsumerGoods("面包" , 1);Producer producer = new Producer(goods);Consumer consumer = new Consumer(goods);Thread thread0 = new Thread(producer);Thread thread1 = new Thread(producer);Thread thread2 = new Thread(consumer);Thread thread3 = new Thread(consumer);thread0.start();thread1.start();thread2.start();thread3.start();}}发生了死锁的现像
这里举例子再解释一下死锁发生的原因,如果 Thread-2 判断 flag == false 进入wait() ,Thread-3 开始执行,也进入到wait(),这时候Thread-2 Thread-3 都处于wait(),这是Thread-0 开始执行判断flag == false,进行生产 ,将 flag 修改为 true,调用监视器的 notify() 唤醒 Thread-2 Thread-3 中的一个,这时 Thread-0 继续执行,判断 flag == true ,进入wait() , 然后 Thread-1 进行执行,判断 flag == true ,进入wait() ,这时 Thread-0 Thread-1 都处于wait() , 但是Thread-2 Thread-3 中有一个之前被 Thread-0 唤醒,如果Thread-2 被唤醒,重新判断 flag == true,进行消费,消费完了之后调用监视器的 notify() 唤醒一个线程,正好唤醒 Thread-3,这时 Thread-3 重新判断 flag == true,进入wait(), Thread-2 如果继续执行 也会判断 flag == true,进入wait(),这样 四个线程就全部进入了wait(),形成了死锁。
这里总结死锁的原因,是因为调用监视器的 notify() 只能唤醒一个线程,如果正好唤醒的是本方的一个线程,那么重新判断 flag ,也会进入到 wait(),导致所有线程都wait(),要解决这个问题,就要在唤醒的时候,至少唤醒一个对方的线程,这样重新判断 flag 才不会直接进入 wait().
我们对上面的代码进行修改,每次唤醒都唤醒全部的线程,这样对方的线程也会被唤醒,继续往下执行,形成不断的唤醒对方的效果,就不会死锁了
public class MultiProducerMultiConsumerGoods1 extends Goods{//false 表示货物是空的 可以继续生产private volatile boolean flag = false;public MultiProducerMultiConsumerGoods1(String type , int beginNumber){this.type = type;this.number = beginNumber;}public synchronized void produce(){String name = Thread.currentThread().getName();//如果不为空 先等待while (flag){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}this.goodName = type + ":" + number;System.out.println(name + " 生产商品 " + goodName);number ++;flag = true;//这里唤醒全部的线程this.notifyAll();}public synchronized void consume(){String name = Thread.currentThread().getName();//如果 flag == false 货物是空的,等待while (!flag){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(name + " 消费商品 " + goodName);flag = false;//这里唤醒全部的线程this.notifyAll();}}
public class MultiProducerMultiConsumerTest2 {public static void main(String[] args) {Goods goods = new MultiProducerMultiConsumerGoods1("面包" , 1);Producer producer = new Producer(goods);Consumer consumer = new Consumer(goods);Thread thread0 = new Thread(producer);Thread thread1 = new Thread(producer);Thread thread2 = new Thread(consumer);Thread thread3 = new Thread(consumer);thread0.start();thread1.start();thread2.start();thread3.start();}}没有发生 连续生产 连续消费 死锁 等问题
这里可能会有人这样想,一下子把所有的线程都唤醒了,这样对cpu的线程资源和计算资源太浪费了吧,可不可以在多生产者多消费者的情况下,只唤醒一个对方线程,而不是把所有的线程都唤醒呢,这样的想法确实很好,我们尝试可不可以用两个锁,一个专门用于生产线程的同步,一个专门用于消费线程的同步,我先把这个想法的代码写出来,能不能用再说
public class MultiProducerMultiConsumerGoods2 extends Goods{//false 表示货物是空的 可以继续生产private volatile boolean flag = false;//本来想法是专门限制生产的锁private Object produceLock = new Object();//本来想法是专门限制消费的锁private Object consumeLock = new Object();public MultiProducerMultiConsumerGoods2(String type , int beginNumber){this.type = type;this.number = beginNumber;}public void produce(){String name = Thread.currentThread().getName();//生产线程获取生产锁,然后进入进行生产或者 等待synchronized(this.produceLock) {//如果不为空 先等待while (flag) {try {this.produceLock.wait();} catch (InterruptedException e) {e.printStackTrace();}}this.goodName = type + ":" + number;System.out.println(name + " 生产商品 " + goodName);number++;flag = true;//本来想法是生产完了,通过消费锁唤醒一个消费线程,避免唤醒本方线程 //和 对方过多的线程this.consumeLock.notify();}}public void consume(){String name = Thread.currentThread().getName();//消费线程获取消费锁,然后进入进行消费或者 等待synchronized(this.consumeLock) {//如果 flag == false 货物是空的,等待while (!flag) {try {this.consumeLock.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(name + " 消费商品 " + goodName);flag = false;//本来想法是消费完了,通过生产锁唤醒一个生产线程,避免唤醒本方线程 //和 对方过多的线程this.produceLock.notify();}}}
public class MultiProducerMultiConsumerTest3 {public static void main(String[] args) {Goods goods = new MultiProducerMultiConsumerGoods2("面包" , 1);Producer producer = new Producer(goods);Consumer consumer = new Consumer(goods);Thread thread0 = new Thread(producer);Thread thread1 = new Thread(producer);Thread thread2 = new Thread(consumer);Thread thread3 = new Thread(consumer);thread0.start();thread1.start();thread2.start();thread3.start();}}Thread-0 生产商品 面包:1
Thread-3 消费商品 面包:1
Exception in thread "Thread-0" Exception in thread "Thread-3" java.lang.IllegalMonitorStateExceptionat java.lang.Object.notify(Native Method)at com.fll.test.multi_thread.producer_consumer.MultiProducerMultiConsumerGoods2.produce(MultiProducerMultiConsumerGoods2.java:40)at com.fll.test.multi_thread.producer_consumer.Producer.run(Producer.java:15)at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateExceptionat java.lang.Object.notify(Native Method)at com.fll.test.multi_thread.producer_consumer.MultiProducerMultiConsumerGoods2.consume(MultiProducerMultiConsumerGoods2.java:66)at com.fll.test.multi_thread.producer_consumer.Consumer.run(Consumer.java:15)at java.lang.Thread.run(Thread.java:748)我们可以看到报出了 IllegalMonitorStateException ,
因为一个synchronized代码块只能指定一个监视器,
并且当一个线程在获取到监视器进入同步代码块里面的时候,
只能调用所进入的synchronized代码块所指定的监视器的 wait() notify() notifyAll(),
所以当生产线程获取到生产监视器 produceLock,进入synchronized 代码块,
生产完了调用 consumeLock 的 notify() 的时候就会报错
看来通过 synchronized 无法实现只唤醒对方线程的操作,但是jdk1.5 出来新的API,Lock 锁提供了相应的实现,我们先来看看代码怎么实现
public class MultiProducerMultiConsumerGoods3 extends Goods{//false 表示货物是空的 可以继续生产private volatile boolean flag = false;private Lock lock = new ReentrantLock();// 同一个Lock锁对象可以创建多个与其关联的 监视器对象//专门限制生产的 监视器private Condition produceCondition = lock.newCondition();//专门限制消费的 监视器private Condition consumeCondition = lock.newCondition();public MultiProducerMultiConsumerGoods3(String type , int beginNumber){this.type = type;this.number = beginNumber;}public void produce(){String name = Thread.currentThread().getName();//生产线程获取生产锁,然后进入进行生产或者 等待lock.lock();try {//如果不为空 先等待while (flag) {try {//await() 方法和 Object 的wait() 都会释放cpu执行权//并且会释放锁this.produceCondition.await();} catch (InterruptedException e) {e.printStackTrace();}}this.goodName = type + ":" + number;System.out.println(name + " 生产商品 " + goodName);number++;flag = true;//生产完了,通过消费 监视器 唤醒一个消费线程,避免唤醒本方线程 和 对方过多的线程this.consumeCondition.signal();}finally {lock.unlock();}}public void consume(){String name = Thread.currentThread().getName();//消费线程获取消费锁,然后进入进行消费或者 等待lock.lock();try {//如果 flag == false 货物是空的,等待while (!flag) {try {//await() 方法和 Object 的wait() 都会释放cpu执行权//并且会释放锁this.consumeCondition.await();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(name + " 消费商品 " + goodName);flag = false;//消费完了,通过生产 监视器 醒一个生产线程,避免唤醒本方线程 和 对方过多的线程this.produceCondition.signal();}finally {lock.unlock();}}}
public class MultiProducerMultiConsumerTest4 {public static void main(String[] args) {Goods goods = new MultiProducerMultiConsumerGoods3("面包" , 1);Producer producer = new Producer(goods);Consumer consumer = new Consumer(goods);Thread thread0 = new Thread(producer);Thread thread1 = new Thread(producer);Thread thread2 = new Thread(consumer);Thread thread3 = new Thread(consumer);thread0.start();thread1.start();thread2.start();thread3.start();}}没有出现任何问题,说明这种解决方案是可以的,也是目前看来最好的解决方案