美文网首页
线程池系列(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