在应用程序中恰当地使用线程池技术可以大大提升应用处理业务的效率。我们应该始终使用线程池来创建、管理线程,而不是在程序中显式地使用new Thread(Runnable)
的方式来创建线程,因为使用线程池可以对线程进行统一管理:创建、销毁线程开销较大,线程池可以对线程池中的线程进行复用,无需反复创建和销毁线程。
Java中通用的创建线程池的类应该是java.util.concurrent
包下面的ThreadPoolExecutor
类;当然Java提供了java.util.concurrent.Executors
工具类来帮助我们快速创建各种定义好的线程池,例如:
Executors.newFixedThreadPool(int nThreads)
——创建一个固定数量线程的线程池。Executors.newSingleThreadExecutor()
——创建一个只包含单个线程的线程池,可以保证所有任务按照指定顺序(FIFO,LIFO, 优先级)始终被同一个线程执行。Executors.newCachedThreadPool()
——创建一个可缓存的线程池,该线程池会根据任务的需要动态创建、回收线程。Executors.newScheduledThreadPool(int corePoolSize)
——创建一个具有定时功能的线程池,适用于执行定时任务。
以上四种定义好的线程池在某些场景确实很方便我们的使用,但是我们需要根据自己的使用场景,有所保留地使用它们,因为如果盲目使用可能会导致隐患,例如以下问题:
FixedThreadPool
和SingleThreadPool
所允许的工作队列最大容量为Integer.MAX_VALUE
,这有可能会随着工作队列中的任务堆积而导致OOM
;
CachedThreadPool
和ScheduledThreadPool
所允许创建的线程数量为Integer.MAX_VALUE
,这也有可能因为创建大量线程导致OOM
或者线程切换开销巨大。
以上四种线程池实际上是对ThreadPoolExecutor
类进行了一个封装,我们最好能直接了解并使用ThreadPoolExecutor
类来创建线程池,这样可以控制线程数量、工作队列大小以及自定义的饱和策略等。
ThreadPoolExecutor
类的构造函数如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
构造函数的参数说明如下:
corePoolSize
——线程池中的核心线程数,一旦创建就会一直保留在线程池中(除非将allowCoreThreadTimeout
参数设置成true
)maximumPoolSize
——线程池中允许存活的最大线程数keepAliveTime
——当创建的线程数量超过了核心线程数,允许线程池中处于空闲状态的线程的最大存活时间unit
——keepAliveTime
参数的单位workQueue
——工作队列,用于存放将被执行的线程任务(Runnable tasks
)threadFactory
——创建线程的工厂,用于标记区分不同线程池所创建出来的线程handler
——当线程数或者工作队列达到上限时,线程池的饱和策略处理逻辑handler
线程池内部的处理逻辑:
- 当线程池中的线程数量小于核心线程数:新提交一个任务时,无论是否存在空闲的线程,线程池都将新建一个新的线程来执行新任务;
- 当线程池中的线程数量大于或等于核心线程数:新提交的任务会被存储到工作队列中,等待空闲线程来执行,而不会创建新线程;
- 当工作队列已满,并且线程数量小于最大线程数的时候,如果继续提交新的任务,线程池会创建新线程来处理任务;
- 当工作队列已满,并且线程数量已达到最大值:继续提交新任务时,线程池会触发拒绝策略处理逻辑;
- 当任务执行完,线程空闲超过
keepAliveTime
时间后,线程池会销毁处于空闲状态的线程,但是会保留corePoolSize
个数的线程。
总结:
新任务提交后,线程池会优先创建新的线程来处理,只有当非空闲的线程数量达到了核心线程数的时候,才会将后续提交的任务存储到工作队列中,直到工作队列积满并且在非空闲线程数小于最大线程数时,线程池又会创建新的线程来处理任务。最后当工作队列和非空闲线程都达到了上限,则触发拒绝策略逻辑。
测试代码:java-thread-pool