线程池死锁

使用线程池的风险

死锁:虽然死锁可能发生在任何多线程程序中,但线程池引入了另一种死锁情况,其中所有正在执行的线程都在等待等待队列阻塞线程的结果,因为执行的线程不可用。

简言之:业务线程在占用了线程池内所有的资源后又向线程池提交了新的任务,并且要等这些任务完成后才释放资源,而这些新提交的任务根本就没机会被完成

线程泄漏:如果从池中删除线程以执行任务但在任务完成时未返回线程,则会发生线程泄漏。例如,如果线程抛出异常并且池类没有捕获此异常,则线程将简单地退出,从而将线程池的大小减小一。如果重复多次,则池最终将变为空,并且没有线程可用于执行其他请求。

资源抖动:如果线程池大小非常大,则在线程之间的上下文切换中浪费时间。如上所述,拥有比最佳数量更多的线程可能导致饥饿问题导致资源颠簸。

线程池自己引发的死锁

正在执行的线程都在等待等待队列阻塞线程的结果而死锁的例子,它证明了单个线程池使用不当时也会引发死锁。假设你有一个 ExecutorService,和之前一样,按照下面的方式运行。

1
2
3
4
5
6
7
8
9
10
ExecutorService pool = Executors.newFixedThreadPool( 10 );
pool.submit(() -> {
     try {
         log.info( "First" );
         pool.submit(() -> log.info( "Second" )).get();
         log.info( "Third" );
     } catch (InterruptedException | ExecutionException e) {
         log.error( "Error" , e);
     }
});

看起来没什么问题 —— 所有信息按照预期的样子呈现在屏幕上:

1
2
3
INFO [pool-1-thread-1]: First
INFO [pool-1-thread-2]: Second
INFO [pool-1-thread-1]: Third

注意我们用 get() 阻塞线程,在显示“Third”之前必须等待内部线程(Runnable)运行完成。这是个大坑!等待内部任务完成意味着需要从线程池额外获取一个线程来执行任务。然而,我们已经使用到了一个线程,所以内部任务在获取到第二个线程前将一直阻塞。当前我们的线程池足够大,运行没问题。让我们稍微改变一下代码,将线程池缩减到只有一个线程,另外关键的一点是我们移除 get() 方法:

1
2
3
4
5
6
ExecutorService pool = Executors.newSingleThreadExecutor();
pool.submit(() -> {
     log.info( "First" );
     pool.submit(() -> log.info( "Second" ));
     log.info( "Third" );
});

代码正常运行,只是有些乱:

1
2
3
INFO [pool-1-thread-1]: First
INFO [pool-1-thread-1]: Third
INFO [pool-1-thread-1]: Second

两点需要注意:

  • 所有代码运行在单个线程上(毫无疑问)
  • “Third”信息显示在“Second”之前

顺序的改变完全在预料之内,没有涉及线程间的竞态条件(事实上我们只有一个线程)。仔细分析一下发生了什么:我们向线程池提交了一个新任务(打印“Second”的任务),但这次我们不需要等待这个任务完成。因为线程池中唯一的线程被打印“First”和“Third”的任务占用,所以这个外层任务继续执行,并打印“Third”。当这个任务完成时,将单个线程释放回线程池,内部任务最终开始执行,并打印“Second”。那么死锁在哪里?来试试在内部任务里加上 get() 方法:

1
2
3
4
5
6
7
8
9
10
ExecutorService pool = Executors.newSingleThreadExecutor();
pool.submit(() -> {
     try {
         log.info( "First" );
         pool.submit(() -> log.info( "Second" )).get();
         log.info( "Third" );
     } catch (InterruptedException | ExecutionException e) {
         log.error( "Error" , e);
     }
});

死锁出现了!我们来一步一步分析:

  • 打印“First”的任务被提交到只有一个线程的线程池
  • 任务开始执行并打印“First”
  • 我们向线程池提交了一个内部任务,来打印“Second”
  • 内部任务进入等待任务队列。没有可用线程因为唯一的线程正在被占用
  • 我们阻塞住并等待内部任务执行结果。不幸的是,我们等待内部任务的同时也在占用着唯一的可用线程
  • get() 方法无限等待,无法获取线程
  • 死锁

这是否意味单线程的线程池是不好的?并不是,相同的问题会在任意大小的线程池中出现,只不过是在高负载情况下才会出现,这维护起来更加困难。你在技术层面上可以使用一个无界线程池,但这样太糟糕了。

https://www.breakyizhan.com/java/4965.html

http://www.importnew.com/30277.html

猜你喜欢

转载自www.cnblogs.com/twoheads/p/10729240.html