我在使用基于虚拟线程的执行器时遇到了死锁。当我使用 50 个线程但不是 20 个线程时会出现此问题。当我不使用虚拟线程时,此问题不会出现。
我正在寻找有关故障排除和解决虚拟线程特有的死锁问题的见解和建议,特别是考虑到在不使用虚拟线程时不存在此问题。
private static final ExecutorService taskExecutor =0 Executors.newFixedThreadPool(CONCURRENT_WORKERS);
对
private static final ExecutorService taskExecutor = Executors.newFixedThreadPool(CONCURRENT_WORKERS, Thread.ofVirtual().factory());
执行器服务包装在 CompletionService 中。
private static final CompletionService<Data> completionService = new ExecutorCompletionService<>(taskExecutor)
主线程似乎在第一次调用时无限期地等待
completionService.take()
。
这是相关代码片段。
for (int taskIndex = 0; taskIndex < TASK_COUNT; taskIndex++) {
completionService.submit(this::doTask);
}
log.info("Submitted all {} tasks to the executor, waiting.", TASK_COUNT);
try {
for (int taskIndex = 0; taskIndex < TASK_COUNT; taskIndex++) {
Future<Data> future = completionService.take();
try {
var data = future.get();
storeData(data);
} catch (ExecutionException e) {
log.warn("Error executing task", e);
}
}
}
问题的原因可能是什么?
编辑:这似乎是由 HTTP 连接池问题引起的。我正在使用httpclient-4.5.14。
在内部,这个客户端使用
synchronized
,根据我的理解,这是虚拟线程的一个问题,因为它固定平台线程。我不知道这是否是死锁的原因,因为我看不到任何错误,线程转储中也没有看到奇怪的东西。
试验:我尝试使用 -Djdk.tracePinnedThreads=full 来检查固定线程,但到目前为止还没有成功。
固定是使用虚拟线程的一个挥之不去的影响,不幸的是可能会导致严重的死锁情况。我在使用各种外部库(例如 Apache HTTP 4.X 客户端、加载缓存和 Google Cloud Storage Java 客户端)时遇到了这些死锁。为了解决这些问题,我必须:
升级到与虚拟线程兼容的较新版本的库 切换到支持虚拟线程的替代库 至于死锁的原因,我认为它们源于常见的基于锁的逻辑和导致固定的同步块的组合。然而,死锁的发生是间歇性的,这表明该特定跟踪的操作顺序也发挥了作用。
这篇 LinkedIn 帖子提供了一些相关背景: https://www.linkedin.com/posts/efagerho_java-activity-7130107883915988992-S_zP/