一、通过 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;
}
@Override
public void run() {
while (true){
goods.produce();
}
}
}
public class Consumer implements Runnable{
private Goods goods;
public Consumer (Goods goods){
this.goods = goods;
}
@Override
public 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.IllegalMonitorStateException
at 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.IllegalMonitorStateException
at 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();
}
}
没有出现任何问题,说明这种解决方案是可以的,也是目前看来最好的解决方案