【问题标题】:Thread starvation deadlock example线程饥饿死锁示例
【发布时间】:2021-06-25 09:57:14
【问题描述】:

在 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 个线程和两个任务。你有一个可能导致死锁的例子吗?

【问题讨论】:

    标签: java multithreading parallel-processing deadlock synchronized


    【解决方案1】:

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

    CountDownLatch 文档中可以阅读:

    CountDownLatch 使用给定的计数进行初始化。 等待方法 由于调用 countDown() 方法,之后释放所有等待的线程并 任何后续的 await 调用都会立即返回。

    有了这些信息,让我们想象每个任务确实由不同的线程执行。所以每个线程使用来自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 线程,该线程反过来也在等待它们,因此会出现死锁和语句:

    传递给 time 方法的执行器必须允许创建 至少与给定并发级别或测试一样多的线程 永远不会完成。

    所以关于你的问题:

    我不知道为什么这会死锁,例如带 1 一线二 任务。你有一个可以死锁的例子吗?

    让我们说concurrency=2,您在Executor 池和master 线程上有一个线程。 master 线程调用:

    ready.await();
    

    初始化如下:

    CountDownLatch ready = new CountDownLatch(concurrency);
    

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

    start.await();
    

    master 线程调用start.countDown();,但该线程仍在等待ready.await();。所以两个线程都在互相等待,从而导致死锁。

    【讨论】:

      【解决方案2】:

      一个会死锁的简单调用:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-26
        • 1970-01-01
        • 2014-03-13
        • 2016-06-23
        • 1970-01-01
        相关资源
        最近更新 更多