在上篇我们学习了线程池各个参数的含义,线程池任务处理流程,使用线程池的好处等内容,本篇我们学习如何创建一个适合我们业务的线程池。为此,我们有必要先学习一下如何大概确定我们线程池核心线程数、怎么设置阻塞队列的类型与大小、当线程池没有能力处理任务了该如何使用拒绝策略等内容。
合适的线程数量
对于线程池来说,不同的任务类型可能采取不同的线程数量会取得更好的效果。这是因为有的线程任务对CPU消耗大,任务时间却短,有的任务需要访问网络,需要较长时间才能完成任务。如果只是笼统的定义一个固定大小的线程池,往往会出现CPU不繁忙,但线程池的线程已满,不能再接受任务了,此时却有可能有一些不耗时但耗费CPU的任务没机会执行,从而导致CPU资源浪费等情况。因此,我们需要根据业务特点来定义线程池。
对于CPU 密集型任务,比如加密、解密、压缩、计算等一系列需要大量耗费 CPU 资源的任务。对于这样的任务最佳的线程数为 CPU 核心数的 1~2 倍,如果设置过多的线程数,实际上并不会起到很好的效果。假设设置过多线程,首先会耗费更多资源,大量任务占用线程,但CPU资源有限,这就导致了很多线程上下文切换的成本,此时性能可能不升反降。
对耗时IO任务,比如数据库、文件的读写,网络通信等任务,这种任务的特点是并不会特别消耗 CPU 资源,但是 IO 操作很耗时,总体会占用比较多的时间。这种情况应该设置更多的线程来完成任务,当在等待IO的线程挂起后,此时这些线程并不占用CPU资源,但占用了线程资源,如果没有更多的线程,后续的任务没机会执行,CPU资源也白白浪费。此时如果有更多线程,则这些线程可以去执行其它任务,把CPU资源给利用起来,提高系统的总体性能。
《Java并发编程实战》的作者 Brain Goetz 推荐的计算方法:
线程数 = CPU 核心数 *(1+平均等待时间/平均工作时间)
可见,线程数和平均等待时间成正比,和平均工作时间成反比。总体按这个思路来设置线程池线程大小是可行的,实际还可以根据特定的服务器资源进行压测,根据压测结果来调整以达到更高的性能。
线程池阻塞队列
我们知道使用Executors工具类可以方便的创建线程池(不推荐),其内部实际就是使用ThreadPoolExecutor进行创建,只是提供了一些默认参数,不需要开发者去关注而已。这里不展开Executors给我们提供的那几种线程池,这里关注一下这些线程池里面的阻塞队列类型,为我们自定义线程池作参考。
FixedThreadPool |
LinkedBlockingQueue |
SingleThreadExecutor |
LinkedBlockingQueue |
CachedThreadPool |
SynchronousQueue |
SingleThreadScheduledExecutor |
DelayedWorkQueue |
ScheduledThreadPoolExecutor |
DelayedWorkQueue |
首先是FixedThreadPool和SingleThreadExecutor,它们用的阻塞队列都是LinkedBlockingQueue,容量是Integer.MAX_VALUE。因为它们的线程数都是固定的,不能创建非核心线程,因此,队列几乎无限大,核心线程都忙的情况只能把任务放队列,这也是固定线程数的关键。
然后是CachedThreadPool,看名字是一个可缓存的线程池。它的阻塞队列使用的是Synch
标签:Java,自定义,队列,阻塞,任务,线程,CPU,资源 From: https://blog.csdn.net/u012882823/article/details/139797437