概述
线程池就是定义管理几个有限的线程资源,进行循环利用。我们生活中有没有这种例子呢?当然是有的,比如我们银行窗口就是一个有限的资源,在我们处理完事情之后,将资源重新归还,然后重新分配下一个人员处理事情,这中模型就是线程池的模型。
使用线程池的好处
线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
几种常见的线程池
-
newFixedThreadPool(int nThreads)
固定线程nThread
大小的线程池,当线程超出最大时进入等待队列。 -
newCachedThreadPool()
创建可变大小的线程池,根据实际需要调整线程池大小。 -
newScheduledThreadPool(int corePoolSize)
创建corePoolSize
个线程,可支持定时任务,周期性调度 -
newSingleThreadExecutor
创建一个单一线程的线程池
线程池运行原理
首先我们先明确线程池由那几个部分组成
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
corePoolSize
,表示核心线程数,在线程池中最小活跃的线程数量。maximumPoolSize
,表示线程池中最大允许的线程数量。keepAliveTime
,计算多长时间没有新的任务提交线程池,将线程池中活跃线程数减少至最小线程。unit
,计算多长时间没有新任务提交线程池的时间单位workQueue
,线程池的等待队列,当线程池正在执行任务的线程达到核心线程数,等待队列没有满,则进入等待队列进行等待。threadFactory
,创建线程工厂,这里我们通常用默认的就行了Executors.defaultThreadFactory()
RejectedExecutionHandler
,拒绝策略,当线程池中已经到达最大线程数,并且等待队列也已经满了,则采取拒绝策略。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
当一个线程任务提交过来是,将会进行如下判断
- 判断正在工作的线程是否小于
corePoolSize
,如果是则调用线程执行该任务。 - 如果大于
corePoolSize
,判断请求队列是否已满,没满则放入请求队列当中。 - 当请求队列满时,判断是否达到最大线程数
maximumPoolSize
,没到则创建线程从请求队列中执行任务。 - 当请求队列又满时,并且已经是最大线程数了,则进行拒绝策略。
线程池中四种拒绝策略
线程池中所有的拒绝策略都是实现了RejectedExecutionHandler
接口
策略名 | 说明 |
---|---|
AbortPolicy | 直接拒绝执行新的任务,并抛出异常RejectedExecutionException |
DiscardPolicy | 不做任何处理,直接将任务丢失 |
DiscardOldestPolicy | 丢弃掉一个等待时间最长的任务,然后再尝试执行下一个任务,除非线程池已经停止 |
CallerRunsPolicy | 将任务转交给调用executor 的线程,除非该线程已经被销毁,那么该任务也会被抛弃 |
阿里巴巴规约中注意
线程池中不允许使用Executors
去创建,而是通过ThreadPoolExecutor
的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
使用Executors
创建的线程池风险
FixedThreadPool
和SingleThreadPool
: 允许的请求队列是Integer.MAX_VALUE
,可能会堆积大量的请求,从而导致oom
CachedThreadPool
和ScheduledThreadPool
:允许创建线程数量为Integer.MAX_VALUE
,可能会创建大量的线程,从而导致oom
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}