多线程:Java的状态等待超时,但不能返回

多线程:Java的状态等待超时,但不能返回

问题描述:

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

主线:多线程:Java的状态等待超时,但不能返回

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

我怎样才能达到我的要求主线程等待子线程的信号,但有一个有界的超时?

+0

您是否需要使用'Lock'和'Condition'(如在作业中)?有比简单的解决方案,而不是建立这样一个大多数的锁定和条件的尝试机制。 – zapl

+0

你的sharedLock的目的是什么?你想用它来防止什么? –

+0

@NicolasFilotto我想使用信号并等待。 condition.await和condition.signal必须与锁一起使用,或者不能调用await和signal方法。 – Moon

这不是你应该如何做到这一点,你应该:

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

正如你所看到的工作,任务必须不在关键部分内执行,我们只获取锁定以通知主线程。否则,如果在超时之后在临界区内执行任务,主线程仍需要再次获取锁,并且由于锁实际上由子线程拥有,所以它将需要等待直到任务结束使超时完全无用。

注:我改名sharedLocklockReentrantLock是一个排他锁不是共享锁,如果你需要一个共享锁检查类Semaphore定义许可证的总量。

+0

非常感谢,这很有道理。但是我仍然想知道在什么情况下,condition.await(timeout,TimeUnit)会有意义。 await方法在返回之前需要锁。在正常情况下,它从另一个释放锁的线程收到一个信号,所以等待可以返回。但在超时的情况下,另一个线程仍然保持锁定状态。所以await()方法中的超时参数不会生效。 – Moon

+0

非常感谢! – Moon

+0

我看到我的回答曾经是被接受的答案。我很高兴这个答案比我的更有帮助。 Buf如果我的回答对你有点帮助,你会介意upvote和这个吗?我在回答中做了很多工作。谢谢。 – waltersu

您的代码可以用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的状态等待超时,但不能返回

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

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

Java的等待/通知通常是用来解决生产者 - 消费者问题。 而且通常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。如果有其它资源应该得到保护,你有2种选择:
1.如果资源上的操作快捷,像ackQueueDFSOutputStream.java,一起保护它sharedObj内。
2.如果资源上的操作比较耗时,请在sharedObj以外的地方进行操作。