多线程:java条件等待超时但无法返回

时间:2016-06-11 14:22:11

标签: java multithreading concurrency mutex

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秒。

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

3 个答案:

答案 0 :(得分:1)

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

  1. 创建一个ExecutorService(线程池),您应该检查类Executors的方法以选择最适合您的方法,但Executors.newFixedThreadPool是一个好的开始
  2. 将您的任务作为FutureTask提交给线程池
  3. 然后暂停拨打get
  4. 正确管理TimeoutException
  5. 以下是如何做到的:

    // 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();
        }
    }
    

    正如您所看到的那样,任务不能在关键部分内执行,我们只获取锁定以通知主线程。否则,如果您在超时后执行临界区内的任务,主线程仍然需要再次获取锁,并且由于锁实际上是由子线程拥有的,因此无论如何都需要等待,直到任务结束为止。使超时毫无用处。

    NB:我将sharedLock重命名为lock,因为ReentrantLock是一个独占锁而不是共享锁,如果您需要共享锁检查该类Semaphore来定义许可总额。

答案 1 :(得分:1)

您可以使用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但无法返回

那是因为必须释放锁定所以 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 不应该持有太长时间。然后当等待超时时,主线程可以再次保持锁定。

查看生产中的示例:hadoop/hdfs/DFSOutputStream.java 逻辑很简单,生产者创建数据包并将其放入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 之外保护它。

答案 2 :(得分:0)

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