wait、notify和notifyAll

代码

static AtomicInteger number=new AtomicInteger(0);
    //生产者
    public static class Producer implements Runnable {
        List<Integer> data;

        public Producer(List<Integer> data) {
            this.data = data;
        }

        @Override
        public void run() {
            while (true) {
                produce();
            }
        }

        private void produce() {
            synchronized (data) {
                try {
                    while (data.size() == 1) {
                        data.wait();
                    }

                    // 模拟500ms生产一条消息
                    Thread.sleep(500);
                    data.add(number.incrementAndGet());

                    data.notifyAll();
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //消费者
    public static class Consumer implements Runnable {
        List<Integer> data;

        public Consumer(List<Integer> data) {
            this.data = data;
        }

        @Override
        public void run() {
            while (true) {
                consume();
            }
        }

        private void consume() {
            synchronized (data) {
                try {
                    while (data.isEmpty()) {
                        data.wait();
                    }
                    System.out.println("Consumer consumed [" + data.remove(0) + "]");
                    data.notifyAll();
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        List<Integer> data = new ArrayList();
        new Thread(new Consumer(data)).start();
        new Thread(new Producer(data)).start();
    }

这段代码有两个注意点:

  • 1.wait()方法外面用while循环而不是if判断
  • 2.用notifyAll()方法,不用notify()。

while循环

A线程调用wait()方法肯定是因为某个指定的条件不为true,从而要等待该条件为真并有线程执行notify()或者notifyAll()时才执行后续代码。如果使用if判断,当在某个线程B调用notify()或者notifyAll()时代码就会继续执行。初看好像没什么问题,但是在多线程的情况下,当线程B的方法并未将指定条件变为真,或者变为真后,在执行线程A时执行了线程C(线程A、C竞争时C获胜)C将条件又变为false,那么在执行A时如果不重新判断条件就可能会造成严重后果。

在本例中,如果只有一个生产者线程,一个消费者线程,那其实是可以用if代替while的,因为线程调度的行为是开发者可以预测的,生产者线程只有可能被消费者线程唤醒,反之亦然,因此被唤醒时条件始终满足,程序不会出错。但是这种情况只是多线程情况下极为简单的一种,更普遍的是多个线程生产,多个线程消费,那么就极有可能出现唤醒生产者的是另一个生产者或者唤醒消费者的是另一个消费者,这样的情况下用if就必然会现类似过度生产或者过度消费的情况了,典型如IndexOutOfBoundsException的异常。所以所有的java书籍都会建议开发者永远都要把wait()放到循环语句里面。

不用notify

在Java的对象锁模型中,JVM会为一个使用内部锁(synchronized)的对象维护两个集合,EntrySet和WaitSet,即锁池和等待池。

需要注意的是,在等待池中的线程是不参与锁竞争的。要想竞争锁必须进入锁池中。对于等待池中的线程,当对象的notify()方法被调用时,JVM会唤醒处于WaitSet中的某一个线程,这个线程会从等待池进入锁池,从而准备竞争锁;或者当notifyAll()方法被调用时,等待池中的全部线程都会被唤醒,从等待池进入锁池,准备竞争锁。

当对象的锁被释放后,那些所有处于锁池中的线程会共同去竞争获取对象的锁,最终会有一个线程(具体哪一个取决于JVM实现)真正获取到对象的锁,而其他竞争失败的线程继续在锁池t中等待下一次机会。

有了这些知识点作为基础,就能解释不用notify的原因了。

如果我们代码中使用了notify()而非notifyAll(),并且有一个生产者两个消费者。我们假设一个可能执行过程。

  • 1.刚开始消费者1拿到了锁,此时等待池为空,锁池中有消费者2和生产者。现在消费者1判断data为空,那么wait(),进入等待池,释放锁。此时等待池中有消费者1,锁池中有生产者和消费者2,生产者和消费者2会竞争锁;

  • 2.然后消费者2拿到了锁,此时等待池中有消费者1,锁池中有生产者。现在消费者1判断data为空,那么wait(),进入等待池,释放锁。此时等待池中有消费者1和消费者2,锁池中有生产者。

  • 3.然后生产者拿到了锁。此时等待池中有消费者1和消费者2,锁池为空。生产者生产完成后执行notify唤醒消费者1,然后执行wait(),进入等待池,释放锁。此时等待池中有生产者和消费者2,锁池中有消费者1。

  • 4.然后消费者1拿到了锁,此时等待池中有生产者和消费者2,锁池为空。现在消费者1判断data非空,进行消费后执行notify唤醒消费者2或者生产者,如果唤醒的是生产者就不会有问题,如果唤醒的是消费者2就会出现问题,假设唤醒消费者2。然后判断data为空,那么wait(),进入等待池,释放锁。此时等待池中有消费者1和生产者,锁池中有消费者2;

  • 5.消费者2拿到了锁,那么等待池中有消费者1和生产者,锁池为为空,此时消费者2判断data为空,直接执行wait(),进入等待池,释放锁。此时锁池为空并等待池中的消费者1和生产者不能参与锁竞争,这样就会导致死锁(消费者1和生产者相互等待);

但如果你把上述例子中的notify()换成notifyAll(),这样的情况就不会再出现了,因为每次notifyAll()都会使其他等待的线程从等待池进入锁池,从而有机会获得锁。

使用notifyAll()的原因就是,notify()非常容易导致死锁。当然notifyAll并不一定都是优点,毕竟一次性将等待池中的线程都唤醒是一笔不菲的开销,如果你能控制你的线程调度,那么使用notify()也是有好处的。

发布了92 篇原创文章 · 获赞 4 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/DingKG/article/details/103028440