可以同时等待和释放底层线程资源的线程间通信机制是啥

Posted

技术标签:

【中文标题】可以同时等待和释放底层线程资源的线程间通信机制是啥【英文标题】:What's the inter-thread communication mechanism that can await and release underlying thread resource at the same time可以同时等待和释放底层线程资源的线程间通信机制是什么 【发布时间】:2020-11-22 09:03:56 【问题描述】:

我正在寻找一种可以同时等待和释放底层线程资源的线程间通信机制。在下面的示例中,当 executorService 仅使用 1 个线程初始化时,第二个任务将被卡住,因为该线程由 t1 持有,即使它是 await。以下代码仅在您更改为使用 2 个线程初始化 executorService 时才有效。

public static void main(String[] args) 
  ExecutorService executorService = Executors.newFixedThreadPool(1);
  CountDownLatch cdl = new CountDownLatch(1);

  executorService.submit(() -> 
    System.out.println("start t1");
    try 
      cdl.await();
     catch (InterruptedException e) 
      e.printStackTrace();
    
    System.out.println("t1 done");
  );

  executorService.submit(() -> 
    System.out.println("start t2");
    cdl.countDown();
    System.out.println("t2 done");
  );

  System.out.println("Master thread ends");

executorService 用 1 个线程初始化时的输出。

start t1
Master thread ends

executorService用2个线程初始化时的输出。

start t1
Master thread ends
start t2
t2 done
t1 done

理想情况下,当 t1 等待时,它不需要持有底层线程,这样 task2 就可以在这个线程池中运行。这个问题的一个实际用例是,如果我有一个线程池,并且任务将被提交/调度回同一个池进行重试。理论上,当所有提交的任务立即失败时,进程将被卡住,因为没有线程可以运行重试任务。

为重试创建一个单独的线程池可以解决这个问题,但是我想知道JAVA是否提供了一种线程间通信机制,允许同时等待和释放底层线程,这样只需要1个线程池。

【问题讨论】:

这听起来像XY problem。你能解释一下你的用例吗? @Turing85,这不是 XY 问题,因为我知道解决方法(具有单独的线程池)有效。但是,我正在寻找更好的解决方案。我的用例是我想要一个单线程池来调度任务并在失败时重试,而我的重试只重试我的部分任务。例如,我有 3 个步骤来完成一项任务。 Step1 有效,Step2 可重试,但失败,但 Step3 不可重试。假设它在 T1 上运行。当 step2 失败时,我想在同一个池中安排它的重试。如果重试成功,T1 应该从 Step3 恢复。我的问题是找到一种方法可以重新释放 T1 以进行重试。 据我所知,一旦任务被线程执行,就不可能“停放”任务。本质上,如果你有一个只有一个线程的ExecutorService,并且该线程执行一个任务,那么直到任务完成或抛出异常,该任务才会释放。因此,当当前执行的任务处于死锁状态时,ExecutorService 和所有计划任务都会卡住。你可以通过wait()的机制来停放线程,但是这样停放的是线程,而不是任务。 “理论上,当所有提交的任务立即失败时,进程将被卡住,因为没有线程可以运行重试任务” - 也许,一个观察者线程在超时后中止失败的任务会起作用吗? 所以这毕竟是一个 XY 问题......也许吧。但要使其正常工作,每个任务都必须定期检查流产(任务不能在外部“杀死”,但必须被要求中止)。另一种解决方案是将一个任务的执行拆分为多个任务。但这些解决方案都不能保证系统会取得进展。反应式编程禁止工作线程上的阻塞操作是有原因的。 【参考方案1】:

释放底层线程资源的唯一方法是完全从任务的main方法中返回(通常是Runnable::run)。要同时等待,事件生产者应该以异步方式订阅。不是每个生产者都有异步接口。 CompletbleFuture 有(方法 whenComplete),但 CountDownLatch 没有。但是,您可以使用异步功能扩展 CountDownLatch,订阅它的完成,从 run() 返回并等待。我在我的DF4J 库中这样做了:AsyncCountDownLatch.java

【讨论】:

以上是关于可以同时等待和释放底层线程资源的线程间通信机制是啥的主要内容,如果未能解决你的问题,请参考以下文章

等待与唤醒机制

等待与唤醒机制(线程之间的通信)

java中wait方法是啥意思

java-等待唤醒机制(线程中的通信)-线程池

线程间通信——等待唤醒机制

JAVA-初步认识-第十四章-线程间通信-等待唤醒机制-代码优化