线程饥饿死锁示例

Thread starvation deadlock example

在 Joshua Bloch 的 Effective Java 一书中,有这个例子(第 81 项):

// Simple framework for timing concurrent execution
public static long time(Executor executor, int concurrency,
Runnable action) throws InterruptedException {
    CountDownLatch ready = new CountDownLatch(concurrency);
    CountDownLatch start = new CountDownLatch(1);
    CountDownLatch done = new CountDownLatch(concurrency);
    for (int i = 0; i < concurrency; i++) {
        executor.execute(() -> {
            ready.countDown(); // Tell timer we're ready
            try {
                start.await(); // Wait till peers are ready
                action.run();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                done.countDown(); // Tell timer we're done
            }
        });
    }
    
    ready.await(); // Wait for all workers to be ready
    long startNanos = System.nanoTime();
    start.countDown(); // And they're off!
    done.await(); // Wait for all workers to finish
    return System.nanoTime() - startNanos;
}

然后说:

传给time方法的executor必须 允许创建至少与给定并发级别一样多的线程,或者 测试永远不会完成。这被称为线程饥饿死锁 [Goetz06, 8.1.1].

我不确定为什么会出现死锁,例如有 1 个线程和两个任务。您有可能导致死锁的示例吗?

一个会死锁的简单调用:

time(Executors.newFixedThreadPool(1), 2, () -> System.out.println("Hello world"));

这里传入了一个只有 1 个线程的执行器。由于 concurrency 是 2,一个线程在“告诉计时器我们准备好了”行等待另一个线程再次减少计数器。

但是没有其他线程来减少计数器,因为只有一个线程。所以唯一的线程被阻塞等待。

TL;DR : concurrency 变量指定 Executor 线程池中的线程要执行的任务数作为参数传递方法 time如果这些任务不是由不同的线程执行,就会发生死锁。

CountDownLatch 文档中可以阅读:

A CountDownLatch is initialized with a given count. The await methods block until the current count reaches zero due to invocations of the countDown() method, after which all waiting threads are released and any subsequent invocations of await return immediately.

有了这些信息,让我们想象一下,每个任务确实是由不同的线程执行的。所以每个线程使用了从Executor到运行的并行工作:

() -> {
            ready.countDown(); // Tell timer we're ready
            try {
                start.await(); // Wait till peers are ready
                action.run();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                done.countDown(); // Tell timer we're done
            }
        }

会先打电话:

ready.countDown(); 

然后 start.await(); 中的 waitmaster 线程调用 start.countDown();(这是唯一执行此操作的线程)。但是,在这样做之前,master 线程正在等待:

ready.await();

剩余线程调用 ready.countDown(); concurrency 次。以便 master 线程可以恢复其工作。因此,如果这些任务中的每一个都不是由 concurrency 个单独的线程执行的,那么这些线程将等待 master 线程,后者反过来也在等待它们,因此会出现死锁和声明:

The executor passed to the time method must allow for the creation of at least as many threads as the given concurrency level, or the test will never complete.

所以关于你的问题:

I am not sure why this would deadlock, e.g. with 1 one thread and two tasks. Do you have an example of a way it could deadlock?

那么假设 concurrency=2,并且您在 Executor 池和 master 线程上有一个线程。 master线程调用:

ready.await();

初始化如下:

CountDownLatch ready = new CountDownLatch(concurrency);

因此 ready.countDown(); 需要至少调用两次。但是,Executor 池中只有一个线程只调用一次 ready.countDown(); 然后等待:

start.await();

master 线程调用 start.countDown();,但该线程仍在等待 ready.await();。所以两个线程都在等待对方,这导致了死锁。