notify导致死锁的示例

      在多线程之间的同步中,一般采用Object.wait()、Object.notify()、Object.notifyAll()来实现,而notify可能会导致多线程之间出现死锁,下面通过生产者消费者的示例来说明:

      生产者代码:

while(true) {
  synchronized (queue) {
     while (queue.size() == maxSize) { #ref_p1
       try {
          System.out.println("已满");
          queue.wait();  #ref_p2
          System.out.println("已唤醒生产者---");
          } catch (InterruptedException e) {
            System.out.println("生产者报错");
          }
     }
     Random random = new Random();
     int i = random.nextInt();
     System.out.println("Produce " + i);
     queue.add(i);
     queue.notify();
  }
}

       消费者代码:

while(true) {
  synchronized (queue) {
    while (queue.isEmpty()) {
      try {
        System.out.println("已空");
        queue.wait();   #ref_c1
        System.out.println("已唤醒消费者---");
      } catch (InterruptedException e) {
         System.out.println("消费者报错");
      }
    }
    int v = queue.remove();
    System.out.println("Consume " + v);
    queue.notify();
  }
}

        队列queue的最大长度设为1,假设有2个生产者线程P1、P2,2个消费者线程C1、C2,黄色表示线程调用wait阻塞,红色表示线程获取锁阻塞。

       1. C1和C2首先执行,由于初始队列为空,故阻塞在#ref_c1处,此时4个线程状态如下: 

notify导致死锁的示例

       2. P1获取到锁执行,往队列中插入数据,并调用notify释放锁,假设此时C1被唤醒,P2获取到锁,则P1在下一次循环中获取锁阻塞

notify导致死锁的示例

       3. 假设P2获取到锁开始执行到#ref_p1处时,C1还没有从队列中获取数据,则此时队列已满,P2在#ref_p2处阻塞(同时释放queue上的锁)

notify导致死锁的示例

       4. C1继续执行,从队列中移除数据,并调用notify,假设该notify唤醒了C2,C2继续执行,由于此时队列已空,故C2在内循环中仍然阻塞在#ref_c1处

notify导致死锁的示例

       5. C1在下一循环中获取到queue上的锁,继续执行,由于此时队列为空,故和C2一样,阻塞在#ref_c1处(同时释放queue上的锁)

notify导致死锁的示例

       6. P1获取到queue上的锁,开始执行,由于此时队列为空,故P1往队列中插入一条数据,并调用notify,假设该notify唤醒了线程P2,P2从#ref_p2处继续开始执行,由于此时队列中有一条数据,即队列已满,故P2在内循环中仍然阻塞在#ref_p2处

notify导致死锁的示例

       7. P1在下一循环中执行到#ref_p1处,由于此时队列已满,故阻塞在#ref_p2处

notify导致死锁的示例

      到此时,4个线程都处于阻塞状态,任何一个都无法被唤醒,即发生了死锁

       从示例中可见,notify导致死锁的原因时只能唤醒一个处于阻塞状态的线程,而解决死锁的方法也很简单,在第6步中如果调用了notifyAll,则会唤醒消费者线程,C1和C2被唤醒之后获取队列中的数据,使得流程可以继续进行下去,从而避免了死锁的发生

上一篇:推荐的 CSS 书写顺序


下一篇:-Xms -Xmx -Xmn -Xss 区别