在递归块中使用 CountDownLatch & Object.wait 挂起

Using CountDownLatch & Object.wait inside recursive block hangs

问题: 在尝试以分阶段方式检索递归块内的值时,执行挂起。

说明: CountDownLatch & Object.wait用于实现递归块内部值的阶段性访问。但是,程序挂起并显示以下输出:

2 < 16
3 < 16
4 < 16
5 < 16
Current total: 5
 Inside of wait
 Inside of wait

程序:[=3​​3=]

import java.util.concurrent.*;
public class RecursiveTotalFinder {
    private static CountDownLatch latch1;
    private static CountDownLatch latch2;
    private static CountDownLatch latch3;
    public static void main(String... args) {
       latch1 = new CountDownLatch(1);
       latch2 = new CountDownLatch(1);
       latch3 = new CountDownLatch(1);

       //Create object
       TotalFinder tf = new TotalFinder(latch1,latch2,latch3);

       //Start the thread
       tf.start();

       //Wait for results from TotalFinder
       try {
           latch1.await();
       } catch(InterruptedException ie) {
           ie.printStackTrace();
       }

       //Print the result after 5th iteration
       System.out.println("Current total: "+tf.getCurrentTotal());
       tf.releaseWaitLock();
       tf.resetWaitLock();

       //Wait for results again
       try {
           latch2.await();
       } catch(InterruptedException ie) {
           ie.printStackTrace();
       }

       //Print the result after 10th iteration
       System.out.println("Current total: "+tf.getCurrentTotal());
       tf.releaseWaitLock();
       tf.resetWaitLock();

       //Wait for results again
       try {
           latch3.await();
       } catch(InterruptedException ie) {
           ie.printStackTrace();
       }

       //Print the result after 15th iteration
       System.out.println("Current total: "+tf.getCurrentTotal());
       tf.releaseWaitLock();
       tf.resetWaitLock();
    }
}


class TotalFinder extends Thread{
    CountDownLatch tfLatch1;
    CountDownLatch tfLatch2;
    CountDownLatch tfLatch3;
    private static int count = 1;
    private static final class Lock { }
    private final Object lock = new Lock();
    private boolean gotSignalFromMaster = false;

    public TotalFinder(CountDownLatch latch1, CountDownLatch latch2, 
                       CountDownLatch latch3) {
        tfLatch1 = latch1;
        tfLatch2 = latch2;
        tfLatch3 = latch3;
    }

    public void run() {
        findTotal(16);
    }

    //Find total
    synchronized void findTotal(int cnt) {
        if(count%5==0) {
           if(count==5)
              tfLatch1.countDown();
           if(count==10)
              tfLatch2.countDown();
           if(count==15)
              tfLatch3.countDown();

           //Sleep for sometime
           try {
               Thread.sleep(3000);
           } catch(InterruptedException ie) {
               ie.printStackTrace();
           }
           //Wait till current total is printed

           synchronized(lock) {
              while(gotSignalFromMaster==false) {
                 try {
                    System.out.println(" Inside of wait");
                    lock.wait();
                 } catch(InterruptedException ie) {
                    ie.printStackTrace();
                 }
              }
              System.out.println("Came outside of wait");
           }

        }
        count +=1;
        if(count < cnt) {
           System.out.println(count +" < "+cnt);
           findTotal(cnt);
        }
    }

    //Return the count value
    public int getCurrentTotal() {
       return count;
    }

    //Release lock
    public void releaseWaitLock() {
        //Sleep for sometime
        try {
            Thread.sleep(5000);
        } catch(InterruptedException ie) {
            ie.printStackTrace();
        }

        synchronized(lock) {
           gotSignalFromMaster=true;
           lock.notifyAll();
        }
    }

    //Reset wait lock
    public void resetWaitLock() {
        gotSignalFromMaster = false;
    }
}

分析: 在我的初步分析中,尽管 notifyAll 是从主程序调用的,但看起来等待是递归发生的。

求助: 为什么在 CountDownLatch 没有生效后使用 notfiyAll 释放锁?需要别人的帮助来理解这个程序到底发生了什么。

我从 JCIP 得到的关于 waitnotify 的主要信息是我可能会错误地使用它们,所以除非绝对必要,否则最好避免直接使用它们。因此,我认为您应该重新考虑使用这些方法。

在这种情况下,我认为您可以使用 SynchronousQueue 更优雅地完成它。也许这样的事情可能会奏效:

import java.util.concurrent.*;
public class RecursiveTotalFinder {
    public static void main(String... args) throws InterruptedException {
       SynchronousQueue<Integer> syncQueue = new SynchronousQueue<>();

       //Create object
       TotalFinder tf = new TotalFinder(syncQueue, 5);

       //Start the thread
       tf.start();

       for (int i = 0; i < 3; ++i) {
         System.out.println("Current total: " + syncQueue.take());
       }
    }
}

class TotalFinder extends Thread{
  private final SynchronousQueue<Integer> syncQueue;
  private final int syncEvery;
  private int count;

  public TotalFinder(SynchronousQueue<Integer> syncQueue, 
                     int syncEvery) {
    this.syncQueue = syncQueue;
    this.syncEvery = syncEvery;
  }

  public void run() {
    try {
      findTotal(16);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new RuntimeException(e);
    }
  }

  //Find total
  void findTotal(int cnt) throws InterruptedException {
    if((count > 0) && (count%syncEvery==0)) {
      syncQueue.put(count);
    }
    count +=1;
    if(count < cnt) {
      System.out.println(count +" < "+cnt);
      findTotal(cnt);
    }
  }
}

至于为什么你原来的方法行不通,是因为主线程把gotSignalFromMaster设置为true,然后马上又回到false,这个先于另一个线程能够检查它的值。如果您在 resetWaitLock 中稍作休眠,它会继续超出当前挂起的点;但是,它然后挂在最后而不是终止。

请注意,必须使用 Thread.sleep 来等待另一个线程更改某些状态是一种糟糕的方法 - 尤其是因为它会使您的程序非常慢。使用同步实用程序可以使程序更快、更容易推理。