【问题标题】:multithread: java condition await timeout but can't return多线程:java条件等待超时但无法返回
【发布时间】:2016-06-11 14:22:11
【问题描述】:
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秒。

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

【问题讨论】:

  • 您是否需要使用LockCondition(例如在作业中)?有比在锁和条件之外构建这样一个尝试最多的机制更简单的解决方案。
  • sharedLock 的用途是什么?你想用它来防止什么?
  • @NicolasFilotto 我想使用信号并等待。 condition.await和condition.signal必须和lock一起使用,否则不能调用await和signal方法。
  • @zapl 我需要一个超时重试机制,除了使用自旋锁之外,你还有其他解决方案吗(比如 while(!isFinished && waitedTime
  • 这不是我的问题我知道什么是锁,我不知道/不知道你想用它做什么

标签: java multithreading concurrency mutex


【解决方案1】:

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

  1. 为此创建一个ExecutorService(线程池),您应该检查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);

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

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

主线程:

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 是排他锁而不是共享锁,如果您需要共享锁检查类Semaphore 来定义许可证总数。

【讨论】:

  • 非常感谢,这很有意义。但我仍然想知道在哪种情况下 condition.await(timeout, TimeUnit) 会有意义。 await 方法需要在返回之前请求锁。在正常情况下,它会收到来自释放锁的其他线程的信号,因此等待可以返回。但是在超时的情况下,另一个线程仍然持有锁。所以 await() 方法中的超时参数不会生效。
  • 我看到我的答案曾经是被接受的答案。我很高兴这个答案比我的更有帮助。如果我的回答对你有点帮助,你会介意那个和这个吗?我在我的回答中做了很多工作。谢谢。
【解决方案2】:

您的代码可以使用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条件等待超时但无法返回

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

    //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 大部分时间都是解锁的!

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

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-27
      • 1970-01-01
      • 2017-12-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-03
      相关资源
      最近更新 更多