为什么多生产消费者要使用notifyAll而不是notify

Posted 小猪快跑22

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么多生产消费者要使用notifyAll而不是notify相关的知识,希望对你有一定的参考价值。

notify 和 notifyAll 的区别?

1.线程调用了wait()方法,便会释放锁,并进入等待池中,不会参与锁的竞争

2. 调用notify()后,等待池中的某个线程(只会有一个)会进入该对象的锁池中参与锁的竞争,若竞争成功,获得锁,竞争失败,继续留在锁池中等待下一次锁的竞争。

3.调用notifyAll()后,等待池中的所有线程都会进入该对象的锁池中参与锁的竞争。

锁池和等待池

锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块)时就必须先获得该对象的锁,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.

=注意=,执行notify方法后并不是立马释放锁,而是在synchronized方法或synchronized代码块执行完成之后才会释放。

如下例子:

 Object lock = new Object();

        Thread waitThread = new Thread("waitThread") {
            @Override
            public void run() {
               synchronized (lock) {
                   System.out.println(Thread.currentThread().getName()+"  start");
                   try {
                       lock.wait();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(Thread.currentThread().getName()+"  end ");
               }
            }
        };

        Thread notifyThread = new Thread("notifyThread") {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName()+" notify  start");
                    lock.notify();
                    System.out.println(Thread.currentThread().getName()+"notify ----");
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"notify end");
                }
            }
        };

        waitThread.start();
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        notifyThread.start();

运行结果如下:

waitThread  start
notifyThread notify  start
notifyThreadnotify ----
notifyThreadnotify end
waitThread  end 

可以看到 并不是 调用notify后立马就释放锁,而是 synchronized 代码块执行完才会释放锁。

单生产、消费者使用 notify

消费者

public class Consumer implements Runnable{

    private final Object lock;
    private List<String> data;

    public Consumer(Object lock, List<String> data) {
        this.lock = lock;
        this.data = data;
    }

    private void get() {
        synchronized (lock) {        
            try {
                while (data.size() == 0) {
                    lock.wait();                  
                }
                String val = data.remove(0);
                System.out.println(Thread.currentThread().getName() + " 消费了数据  " +val);
                lock.notify();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void run() {
        for (int i = 0; i < 4; i++) {
            get();
        }
    }
}

生产者

public class Producer implements Runnable{

    private final Object lock;
    private List<String> data;

    public Producer(Object lock, List<String> data) {
        this.lock = lock;
        this.data = data;
    }

    private void put() {
        synchronized (lock) {
            try {
                while (data.size() == 1) {
                    lock.wait();
                }
                int random = new Random().nextInt(100);
                System.out.println(Thread.currentThread().getName() + " 生产了数据 " + random);
                data.add(String.valueOf(random));
                lock.notify();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void run() {
        int i = 0;
        while (i < 4) {
            i++;
            put();
        }
    }
}

测试代码:

  Object lock = new Object();
        List<String> list = new ArrayList<>();
        new Thread(new Consumer(lock, list), "消费者线程1 >>> ").start();
//        new Thread(new Consumer(lock, list), "消费者线程2 >>> ").start();
        new Thread(new Producer(lock, list), "生产者线程 ---").start();

结果如下:

生产者线程 --- 生产了数据 42
消费者线程1 >>>  消费了数据  42
生产者线程 --- 生产了数据 69
消费者线程1 >>>  消费了数据  69
生产者线程 --- 生产了数据 9
消费者线程1 >>>  消费了数据  9
生产者线程 --- 生产了数据 87
消费者线程1 >>>  消费了数据  87

从结果上看是没问题的,下面看看多生产、消费者模式

2个生产者2个消费者

其他的代码不变,只改变测试代码;结果发现执行到下面就出现一种假死状态了。

生产者线程1 --- 生产了数据 42
消费者线程1 >>>  消费了数据  42
生产者线程2 --- 生产了数据 6

为什么会出现这种状态呢?原因如下:

假设 消费者线程1 ---- A
消费者线程2 ---- B
生产者线程1 ---- C
生产者线程2 ---- D

  1. 假设 A 先获取锁,发现集合 data 里面没有数据 然后 调用 wait 且释放锁
  2. 假设 B 获取了锁,同样,发现集合 data 里面没有数据 然后 调用 wait 且释放锁
  3. 假设 C 先获取锁,然后生产了一条数据存入data,然后调用 notify()去唤醒处于等待池中的线程之一(A 或者 B),接着执行循环,发现data 满了,调用 wait 且释放锁。
  4. 假设 还是 A 先获取了锁,那么 A 消费了数据,然后调用 notify,接着去执行循环,此时发现 data 空了,调用wait 方法且释放锁。 此时A 、B、C都处于等待池。
  5. 由于A、B、C都处于等待池了,那么此时 D 会获取锁,D 生产一条数据,然后调用 notify,接着去循环,然后发现 data 满了,然后调用 wait 且释放锁。
  6. 这时,线程C再次获取了锁,然后发现 data 满了,再次 调用 wait 方法,就会出现 A 、B、C、D 4个线程都处于wait状态了,处于一种假死等待状态。

现在上面的代码 生产者和消费者中的 notify改为 notifyAll() ,那么问题都解决了,结果如下:

生产者线程1 --- 生产了数据 67
消费者线程2 >>>  消费了数据  67
生产者线程2 --- 生产了数据 60
消费者线程1 >>>  消费了数据  60
生产者线程1 --- 生产了数据 64
消费者线程2 >>>  消费了数据  64
生产者线程2 --- 生产了数据 18
消费者线程1 >>>  消费了数据  18
生产者线程1 --- 生产了数据 81
消费者线程2 >>>  消费了数据  81
生产者线程2 --- 生产了数据 59
消费者线程1 >>>  消费了数据  59
生产者线程1 --- 生产了数据 19
消费者线程2 >>>  消费了数据  19
生产者线程2 --- 生产了数据 42
消费者线程1 >>>  消费了数据  42

注意,这里面是2个生产者和2个消费者,如果是1个生产者2个消费者,且里面的循环次数都是一样的话(例子中都是4),那么你会发现也会出现假死现象,原因都是一样的,有兴趣的自己分析下哈。

===但是,现在一般都喜欢用 ReentrantLock 以及 Condition来实现,可以看到Java的好多队列的源码都是这样用的,且不需要调用 signalAll ===

为什么不用signalAll呢?###

因为这里面是可以定向唤醒的,即生产者只会唤醒消费者,消费者只会唤醒生产者,不会发生消费者唤醒另外的消费者。

我也写了例子,如下:

生产者

public class Producer2 implements Runnable{

    private final ReentrantLock lock;
    private Condition notEmpty;
    private Condition notFull;
    private List<String> data;

    public Producer2(ReentrantLock lock, List<String> data, Condition notFull, Condition notEmpty) {
        this.lock = lock;
        this.data = data;
        this.notEmpty = notEmpty;
        this.notFull = notFull;
    }

    private void put() {
        try {
//            System.out.println(Thread.currentThread().getName() + "去生产");
            lock.lock();
            while (data.size() == 1) {
                notFull.await();
            }
            int random = new Random().nextInt(100);
            System.out.println(Thread.currentThread().getName() + " 生产了数据 " + random);
            data.add(String.valueOf(random));
            notEmpty.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void run() {
        int i = 0;
        while (i < 10) {
            i++;
            put();
        }
    }
}

消费者

public class Consumer2 implements Runnable {

    private final ReentrantLock lock;
    private Condition notEmpty;
    private Condition notFull;
    private List<String> data;

    public Consumer2(ReentrantLock lock, List<String> data, Condition notFull, Condition notEmpty) {
        this.lock = lock;
        this.data = data;
        this.notEmpty = notEmpty;
        this.notFull = notFull;
    }


    private void get() {
//        System.out.println(Thread.currentThread().getName() + "去消费");
        try {
            lock.lock();
            while (data.size() == 0) {
                notEmpty.await();
//                System.out.println(Thread.currentThread().getName() + "被唤醒");
            }
            String val = data.remove(0);
            System.out.println(Thread.currentThread().getName() + " 消费了数据  " + val);
            notFull.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            get();
        }
    }
}

调用的地方:

 private static void test2() {
        ReentrantLock reentrantLock = new ReentrantLock();
        Condition notFull = reentrantLock.newCondition();
        Condition notEmpty = reentrantLock.newCondition();

        List<String> list = new ArrayList<>();
        new Thread(new Consumer2(reentrantLock, list, notFull, notEmpty), "消费者线程1 >>> ").start();
        new Thread(new Producer2(reentrantLock, list, notFull, notEmpty), "生产者线程1 >>> ").start();
        new Thread(new Consumer2(reentrantLock, list, notFull, notEmpty), "消费者线程2 >>> ").start();
        new Thread(new Producer2(reentrantLock, list, notFull, notEmpty), "生产者线程2 >>> ").start();

    }

结果如下:

生产者线程1 >>>  生产了数据 38
消费者线程2 >>>  消费了数据  38
生产者线程2 >>>  生产了数据 39
消费者线程1 >>>  消费了数据  39
生产者线程1 >>>  生产了数据 56
消费者线程2 >>>  消费了数据  56
生产者线程2 >>>  生产了数据 3
消费者线程1 >>>  消费了数据  3
生产者线程1 >>>  生产了数据 91
消费者线程2 >>>  消费了数据  91
生产者线程2 >>>  生产了数据 27
消费者线程1 >>>  消费了数据  27
生产者线程1 >>>  生产了数据 87
消费者线程2 >>>  消费了数据  87
生产者线程2 >>>  生产了数据 46
消费者线程1 >>>  消费了数据  46
生产者线程1 >>>  生产了数据 61
消费者线程2 >>>  消费了数据  61
生产者线程2 >>>  生产了数据 55
消费者线程1 >>>  消费了数据  55
生产者线程1 >>>  生产了数据 30
消费者线程2 >>>  消费了数据  30
生产者线程2 >>>  生产了数据 90
消费者线程1 >>>  消费了数据  90
生产者线程1 >>>  生产了数据 79
消费者线程2 >>>  消费了数据  79
生产者线程2 >>>  生产了数据 80
消费者线程1 >>>  消费了数据  80
生产者线程1 >>>  生产了数据 63
消费者线程2 >>>  消费了数据  63
生产者线程2 >>>  生产了数据 24
消费者线程1 >>>  消费了数据  24
生产者线程1 >>>  生产了数据 54
消费者线程2 >>>  消费了数据  54
生产者线程1 >>>  生产了数据 22
消费者线程1 >>>  消费了数据  22
生产者线程2 >>>  生产了数据 11
消费者线程1 >>>  消费了数据  11
生产者线程2 >>>  生产了数据 6
消费者线程2 >>>  消费了数据  6

以上是关于为什么多生产消费者要使用notifyAll而不是notify的主要内容,如果未能解决你的问题,请参考以下文章

基于Java 生产者消费者模式(详细分析)

生产者与消费者问题解决:解决先打印出消费的情况

操作系统王道考研 p22-26 生产者消费者问题多生产者多消费者问题吸烟者问题读者写者问题哲学家进餐问题

C ++ 11中无锁的多生产者/消费者队列

shell-整数测试多范例多生产案例举例

shell-整数测试多范例多生产案例举例