多线程:java 条件等待超时但不能 return

multithread: java condition await timeout but can't return

Lock sharedLock = new ReentrantLock();
Condition condition = lock.newCondition();

主线程:

sharedLock.lock();
childThread.start();
condition.await(5, TimeUnit.SECONDS);
sharedLock.unlock();

子线程:

sharedLock.lock();
//do something, may take a long time
Thread.sleep(10);// sleep to simulate a long execution
condition.signal();
sharedLock.unlock();

假设子线程发送网络请求并等待响应,我希望主线程最多等待5秒,如果超时,重试请求。但是当await()超时时,它无法获得锁,因为子线程仍然持有它,所以它仍然等待锁直到子线程释放它,这需要10秒。

如何实现主线程等待子线程信号但有一个有限超时的要求?

这不是你应该做的,你应该:

  1. 为此创建一个 ExecutorService(线程池),您应该检查 class Executors 的方法以选择最适合您的方法,但 Executors.newFixedThreadPool 是一个好的开始
  2. 将您的任务作为 FutureTask 提交到线程池
  3. 然后用超时调用get
  4. 妥善管理 TimeoutException

这是如何完成的:

// Total tries
int tries = 3;
// Current total of tries
int tryCount = 1;
do {
    // My fake task to execute asynchronously
    FutureTask<Void> task = new FutureTask<>(
        () -> {
            Thread.sleep(2000);
            return null;
        }
    );
    // Submit the task to the thread pool
    executor.submit(task);
    try {
        // Wait for a result during at most 1 second
        task.get(1, TimeUnit.SECONDS);
        // I could get the result so I break the loop
        break;
    } catch (TimeoutException e) {
        // The timeout has been reached
        if (tryCount++ == tries) {
            // Already tried the max allowed so we throw an exception
            throw new RuntimeException(
                String.format("Could execute the task after %d tries", tries),
                e
            );
        }
    }
} while (true);

How can I achieve my requirement that main thread wait child thread's signal, but have a bounded timeout?

您可以通过以下方式实现您的要求:

主线程:

lock.lock();
try {
    childThread.start();
    condition.await(5, TimeUnit.SECONDS);
} finally {
    sharedLock.lock();
}

子线程:

try {
    //do something, may take a long time
    Thread.sleep(10);// sleep to simulate a long execution
} finally {
    // Here we notify the main thread that the task is complete whatever
    // the task failed or not
    lock.lock();
    try {
        condition.signal();
    } finally {
        lock.unlock();
    }
}

正如你所看到的工作,任务一定不能在临界区内执行,我们只是获取锁通知主线程而已。否则,如果超时后在临界区内执行任务,主线程仍将需要再次获取锁,并且由于锁实际上由子线程拥有,因此无论如何它都需要等待直到任务结束使超时完全无用。

注意: 我将 sharedLock 重命名为 lock 因为 ReentrantLock 是排他锁而不是共享锁,如果你需要共享锁检查 class Semaphore 来定义许可总量。

您的代码可以使用 intrinsic lock 来简化。

Object sharedObj = new Object();

主线程:

    synchronized (sharedObj) {
      int retryCount = 0;
      while (retryCount < maxRetry) {
        sharedObj.wait(5000);
        retryCount++;
      }
    }

子线程:

    synchronized (sharedObj) {
      //do something, may take a long time
      Thread.sleep(10);// sleep to simulate a long execution
      sharedObj.notify();
    }

java condition await timeout but can't return

那是因为必须释放锁才能wait/await才能return。所以你的子线程应该是这样的:

    //do something, may take a long time
    Thread.sleep(10);// sleep to simulate a long execution
    synchronized (sharedObj) {
      sharedObj.notify();
    }

Java的wait/notify通常用来解决生产者-消费者问题。 通常 sharedObj 不应该持有太久。然后你的主线程可以在 wait 超时时再次持有锁。

看一个生产中的例子:hadoop/hdfs/DFSOutputStream.java 逻辑很简单,producer创建packet放到dataQueue

// takes a long time to create packet
synchronized (dataQueue) {
  dataQueue.addLast(packet);
  dataQueue.notifyAll();
}

消费者在 dataQueue 为空时等待:

    synchronized (dataQueue) {
      while ((!shouldStop() && dataQueue.size() == 0 &&... ) {
        try {
          dataQueue.wait(timeout);
        } catch (InterruptedException  e) {
          LOG.warn("Caught exception", e);
        }
        doSleep = false;
        now = Time.monotonicNow();
      }

如您所见,dataQueue 大部分时间都处于解锁状态!

How can I achieve my requirement that main thread wait child thread's signal, but have a bounded timeout?

如果您的子线程大部分处于循环中,您的主线程可以设置一个 isRunning 标志来让子线程自行停止。如果您的子线程主要被 I/O 操作阻塞,您的主线程可以中断子线程。

sharedObj用于协调和保护sharedObj。如果还有其他资源需要保护,你有2个选择:
1.如果对资源的操作比较快,比如DFSOutputStream.java中的ackQueue,一起保护在里面sharedObj.
2.如果对资源的操作比较耗时,在sharedObj.

之外进行保护

问题中的有效混淆是因为 "Thread.sleep(10)" 是在锁块内完成的。当 await(long time, TimeUnit unit) 由于超时而不得不 return 时,它仍然需要锁。因此,正如另一个答案中所建议的那样,长 运行 任务不应该在锁内才能正常工作。 但是最好有适当的文档来强调这一事实。例如,如果我们 await(5, TimeUnit.SECONDS) 即等待 5 秒并且锁在调用后 10 秒可用,即使此时锁可用,它仍然 return false共 return.