什么?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()方法,设置等待的超时时间。
方案一必须要这样改动的,方案二可以根据实际情况设置超时时间。
网友评论