前言
我们可以通过 java.util.concurrent.ThreadPoolExecutor 来创建一个线程池:
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, threadFactory, handler);
参数说明:
1、corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
2、maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没用了。
3、keepAliveTime(线程活动时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程利用率。
4、TimeUnit(线程活动时间的单位):可选的单位有天(Days)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)
5、runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列:
(1)ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,FIFO(先进先出)。
(2)LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue,静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
(3)SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool()使用了这个队列。
(4)PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
6、threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
7、RejectedExecutionHandler (饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略还处理新提交的任务。它可以有如下四个选项:
AbortPolicy:直接抛出异常,默认情况下采用这种策略
CallerRunsPolicy:只用调用者所在线程来运行任务
DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
DiscardPolicy:不处理,丢弃掉

按照一般的理解,初始化线程池,只需要一个 maximumPoolSize 入参就行了,corePoolSize 和 maximumPoolSize 似乎有重复的嫌疑(一开始我也是这么以为的),其实不是这样的,下面我们来详细说说这两者的区别和联系。

要理解 这两个参数的区别,首先要知道,关于 ThreadPoolExecutor 相关的任务线程,它包含两部分:(1)正在线程池中运行的任务线程、(2)在taskQueue 中排队等待运行的任务线程。

1、当线程池初始化完成之后,executorService.submit(new Thread(...)); 加入需要运行的任务线程,因为线程池初始化是没有线程运行的,所以当提交一个任务到线程池时,线程池会创建一个线程来直接执行任务;
2、当线程池中正在运行的线程达到 corePoolSize 个时,线程会放到 taskQueue 中排队等候;
3、当 taskQueue(阻塞队列)的容量达到上限(即队列中不能再加入任务线程了),而当前的poolSize(就是正在线程池中运行的任务线程个数)小于 maximumPoolSize 时,则新增线程来处理任务;
4、当 taskQueue 的容量达到上限,且 poolSize = maximumPoolSize,那么线程池已经达到极限,会根据饱和策略RejectedExecutionHandler拒绝新的任务。

这个过程,像极了小朋友做客的场景:

小朋友做客,桌上放了一桌的土豆条,小朋友有一个碗(任务队列 taskQueue),碗里可以放10根土豆条(taskQueue容量 = 10),正常情况下,小朋友每次可以吃2根土豆条(嘴巴就是线程池,corePoolSize = 2),小朋友狼吞虎咽的吃每次可以吃4根土豆条(maximumPoolSize = 4)。
坐在餐桌边后,小朋友开始吃土豆,先往嘴里放了2根土豆条,但怕其他小朋友和自己抢,然后赶紧往自己的碗里加土豆条(加入队列),直到碗满了(现在小朋友嘴里2个土豆条,碗里10个土豆条)。小朋友依然害怕因为自己动作慢比其他人少吃,但是碗已经满了,所以小朋友开始狼吞虎咽地吃(嘴里同时塞4个土豆条,碗里10个土豆条)。此时小朋友已经达到极限了,虽然桌上还有土豆条,但是也只能先拒绝了(饱和策略RejectedExecutionHandler),等嘴巴里的土豆条吃完之后,再去取。

补充:

如果 使用 LinkedBlockingQueue(使用无入参构造) 对象作为 taskQueue,则不容易出现队列满情况,而容易出现内存溢出情况。

05-11 11:23