美文网首页
线程池系列(5)记一次JDK线程池死锁

线程池系列(5)记一次JDK线程池死锁

作者: 小胖学编程 | 来源:发表于2022-03-03 10:38 被阅读0次

什么?JDK线程池还会死锁?

1. 死锁产生的必要条件

产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。

(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

2. JDK线程池如何去写出死锁

JDK线程池有两个作用:一个是并发,一个是限流。当线程池没有资源时,任务就会被放入阻塞队列,等待执行。但是若利用Future的get()/join()方法去阻塞父线程。便有可能触发死锁的四个必要条件。

问题代码:

@Slf4j
public class TestExecutor {

    /**
     * https://www.cnblogs.com/twoheads/p/10729240.html
     */
    private static final ExecutorService VIEW_EXECUTOR = new ThreadPoolExecutor(1,1,1000,
            TimeUnit.SECONDS,new ArrayBlockingQueue<>(2));

    static CompletableFuture busFuture() {
        //rpc调用 服务
        return CompletableFuture.runAsync(() -> {
            log.info("开始执行");
            log.info("执行结束");
        }, VIEW_EXECUTOR);
    }


    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture voidCompletableFuture = CompletableFuture.runAsync(() -> {
                    log.info("----begin");
                    CompletableFuture busFuture = busFuture();
                    busFuture.join();
                    log.info("----end");
                },
                VIEW_EXECUTOR);
        voidCompletableFuture.get();
        log.info("执行完毕");
    }
}

得到的教训:

在复杂的业务场景(复杂的代码逻辑)中,很容易写出以上的代码。这里为了便于测试,给出的核心线程数是1个,但即使真正场景中核心线程数给出n个,但是真正高并发场景中,n个线程会瞬执行voidCompletableFuture,就没有线程资源去执行busFuture。而所有的线程都会在join()处被阻塞。造成死锁。

解决方案:
方案一:将busFuture()中使用一个新的线程池去处理。
方案二:busFuture调用get()方法,设置等待的超时时间。

方案一必须要这样改动的,方案二可以根据实际情况设置超时时间。

相关文章

网友评论

      本文标题:线程池系列(5)记一次JDK线程池死锁

      本文链接:https://www.haomeiwen.com/subject/mokialtx.html