【问题标题】:Causing Deadlock situation when implementing Runnable interface and running fine when extending Thread class实现Runnable接口时导致死锁情况,扩展Thread类时运行良好
【发布时间】:2021-04-21 19:18:25
【问题描述】:

为了理解 wait() 和 notifyAll() 功能,我正在创建一个程序来更好地理解所提到的多线程概念。但不知何故,我注意到以下程序在通过 Thread 类工作并产生预期输出时运行良好。但是当我用 Runnable 接口切换同一个程序时,我遇到了死锁问题。

任何我哪里出错的建议或者我对这个概念缺乏什么理解。

class Test{
  public static void main(String[] args) {
    Notifier notifier = new Notifier();
    new Waiter(notifier, "A").start();
    new Waiter(notifier, "B").start();

    notifier.start();
  }
}

Waiter 类在不同线程上调用 wait()。

class Waiter extends Thread{

  private Notifier notifier;

  Waiter(Notifier notifier, String name){
    super(name);
    this.notifier = notifier;
  }

  public void run(){
    synchronized(notifier){
      System.out.println(Thread.currentThread().getName() + " is trying to call wait()");
      try{
        notifier.wait();
      }catch(InterruptedException e){}
       System.out.println(Thread.currentThread().getName() + " will now continue with remaining code");
    }
  }
}

Notifier 类调用 notifyAll() 并通知所有等待的线程。

class Notifier extends Thread{
  public void run(){
    synchronized(this){
      try{
        Thread.sleep(100);
      }catch(InterruptedException e){}
        System.out.println("Notifying all threads");
        notifyAll();
    }
  }
}

以下程序的O/P是

A is trying to call wait()
B is trying to call wait()
Notifying all threads
B will now continue with remaining code
A will now continue with remaining code

现在同一个程序使用可运行接口

class Test{
  public static void main(String[] args) {
    Notifier notifier = new Notifier();
    // new Waiter(notifier, "A").start();
    // new Waiter(notifier, "B").start();
    //
    // notifier.start();

    new Thread(new Waiter(notifier), "A").start();
    new Thread(new Waiter(notifier), "B").start();
   
    new Thread(notifier).start();
  }
}

class Waiter implements Runnable{

  private Notifier notifier;

  Waiter(Notifier notifier){
    // super(name);
    this.notifier = notifier;
  }

  public void run(){
    synchronized(notifier){
      System.out.println(Thread.currentThread().getName() + " is trying to call wait()");
      try{
        notifier.wait();
      }catch(InterruptedException e){}
       System.out.println(Thread.currentThread().getName() + " will now continue with remaining code");

    }
  }
}

class Notifier implements Runnable{
  public void run(){
    synchronized(this){
      try{
        Thread.sleep(100);
      }catch(InterruptedException e){}
        System.out.println("Notifying all threads");
        notifyAll();
    }
  }
}

以下程序的O/P导致死锁情况

A is trying to call wait()
Notifying all threads
B is trying to call wait()
A will now continue with remaining code

我试图通过使主线程休眠 1ms 并获得所需的输出,在 1 毫秒后启动通知程序线程。

    new Thread(new Waiter(notifier), "A").start();
    new Thread(new Waiter(notifier), "B").start();

     try{
       Thread.sleep(1);
     }catch(InterruptedException e){}

    new Thread(notifier).start();

但是为什么在正常/顺序启动通知程序线程时它不起作用(即没有休眠)。是因为我正在为 Runnable 目的或其他目的创建额外的对象吗?

【问题讨论】:

    标签: java multithreading


    【解决方案1】:

    您的代码存在典型的竞争条件问题。你的两个例子都是相似的。如果名称为 ThreadNotifier 比线程 AB 更快,您将获得 DeadLock 用于使用 Thread 扩展的代码和使用 Runnable 实现的代码。

    如果您的 Notifier 是最后一个,那么您的代码对于这两个示例都将成功。 您可以多次运行代码并重现所有情况。

    对于这个问题,你有很多类型的解决方案,我用两个 CountDownLatch 编写了其中一个。在这个例子中,我们将有一个严格定义的线程执行顺序:

     public class Solution0 {
    
        public static void main(String[] args) throws FileNotFoundException, InterruptedException {
            CountDownLatch countDownLatch1 = new CountDownLatch(1);
            CountDownLatch countDownLatch2 = new CountDownLatch(1);
            new Waiter(countDownLatch1, "A").start();
            while (countDownLatch1.getCount() != 0) {
                Thread.sleep(500);
            }
            new Waiter(countDownLatch2, "B").start();
            while (countDownLatch2.getCount() != 0) {
                Thread.sleep(500);
            }
    
            Notifier notifier = new Notifier();
            notifier.start();
        }
    }
    
    class Waiter extends Thread {
    
        private final CountDownLatch countDownLatch;
    
        Waiter(CountDownLatch countDownLatch, String name) {
            super(name);
            this.countDownLatch = countDownLatch;
        }
    
        public void run() {
            System.out.println(Thread.currentThread().getName() + " is trying to call wait()");
            countDownLatch.countDown();
        }
    }
    
    class Notifier extends Thread {
    
        public synchronized void run() {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
            System.out.println("Notifying all threads");
            notifyAll();
        }
    }
    

    【讨论】:

    • 那么,由于我在方法中使用同步块并且仍然没有获得所需的输出,因此可能是什么解决方案来避免竞争条件
    • 您没有询问解决方案问题,您问的是“为什么”,但是,我编辑了答案并添加了解决方案的示例
    • 感谢您纠正我并提供解决方案...祝您有美好的一天!!!
    • 我不太了解 CountDownLatch 类。尽管您已经提供了解决方案,但它并没有消除我的疑问(因为我想更好地理解 wait() 和 notifyAll()),因为它们在您提供的解决方案中没有 wait(),它正在对 CountDownLatch 对象执行操作而不是在 Notifier 对象上。我希望在我的程序中进行更改,使其仍然在 Notifier 对象上运行,而不是在任何其他对象上运行。我可以在几毫秒后启动通知程序线程以获得适当的输出(已在我的问题的最后一部分中描述)吗?
    【解决方案2】:

    我找到了未获得所需输出的原因。这是因为我正在对通知对象进行操作。现在,当我们在程序中启动多个线程时,它们会随机而不是按顺序执行,因为线程调度程序(JVM 的一部分)决定此时应该运行哪个线程,并且不能保证线程将按什么顺序运行。

    此外,每个对象都有一个唯一的锁。在对任何对象调用 wait()、notify() 和 notifyAll() 时,线程应该是该对象的所有者,即线程必须拥有该对象的锁。现在,当一个线程(在我们的例子中是 Waiter 线程)在某个对象(Notifier 是我们例子中的对象)上调用 wait() 时,

      class Waiter implements Runnable{
    
      private Notifier notifier;
    
      Waiter(Notifier notifier){
        this.notifier = notifier;
      }
    
      public void run(){
        synchronized(notifier){
          System.out.println(Thread.currentThread().getName() + " is trying to call wait()");
          try{
            notifier.wait();
          }catch(InterruptedException e){}
           System.out.println(Thread.currentThread().getName() + " will now continue with remaining code");
    
        }
      }
    }
    

    然后该线程立即释放锁并进入等待状态,以便其他线程可以处理该对象。现在,假设 Notifier 线程有机会运行(由 Thread Scheduler 提供)并获得了锁。

    class Notifier implements Runnable{
      public void run(){
        synchronized(this){
          try{
            Thread.sleep(100);
          }catch(InterruptedException e){}
            System.out.println("Notifying all threads");
            notifyAll();
        }
      }
    

    现在,在完成它的任务后,它(Notifier 线程)通知所有线程(通过调用 notifyAll())并释放锁,以便其他线程(Waiter 线程)可以继续它们的进一步执行。当其他 Waiter 线程开始执行时,它们进入同步块并调用 wait() 并且它们必须等待终身(或直到我们异常退出程序),因为没有人通知它们,因为 Notifier 线程已经执行并且这会导致死锁情况。

    现在,如果我们在一段时间(毫秒)后启动 Notifier 线程,这个问题就可以解决,这样所有线程(Waiter 线程)就可以在 Notifier 之前一一开始执行,在 Notifier 对象上调用 wait()线程已启动,将产生所需的输出。

      new Thread(new Waiter(notifier), "A").start();
      new Thread(new Waiter(notifier), "B").start();
    
       try{
         Thread.sleep(1);
       }catch(InterruptedException e){}
    
      new Thread(notifier).start();
    

    下面是固定程序

    class Test{
      public static void main(String[] args) {
        Notifier notifier = new Notifier();
        new Thread(new Waiter(notifier), "A").start();
        new Thread(new Waiter(notifier), "B").start();
    
        try{
          Thread.sleep(1);
        }catch(InterruptedException e){}
    
        new Thread(notifier, "Notifier").start();
      }
    }
    
    class Waiter implements Runnable{
    
      private Notifier notifier;
    
      Waiter(Notifier notifier){
        this.notifier = notifier;
      }
    
      public void run(){
        synchronized(notifier){
          System.out.println(Thread.currentThread().getName() + " is trying to call wait()");
          try{
            notifier.wait();
          }catch(InterruptedException e){}
            System.out.println(Thread.currentThread().getName() + " will now continue with remaining code");
          }
        }
      }
    
      class Notifier implements Runnable{
        public void run(){
          synchronized(this){
            try{
              Thread.sleep(2000);
            }catch(InterruptedException e){}
              System.out.println("Notifying all threads");
              notifyAll();
            }
          }
        }
    

    以及所需的输出:

    A is trying to call wait()
    B is trying to call wait()
    Notifying all threads
    B will now continue with remaining code
    A will now continue with remaining code
    

    【讨论】:

      猜你喜欢
      • 2012-02-15
      • 1970-01-01
      • 2011-02-16
      • 1970-01-01
      • 2015-11-21
      • 1970-01-01
      • 2016-11-09
      • 2013-05-05
      • 1970-01-01
      相关资源
      最近更新 更多