线程池原理详解
线程池原理详解
线程池在并发编程中使用的较多,线程池可以减少线程创建的开销,池化资源,下面就对线程池的原理做一个简单的分析。
源码讲解
我们使用线程池,最推荐的做法是手动创建线程池,代码如下:
private static final Executor WORK_THREAD = new ThreadPoolExecutor(10, 10,
0, TimeUnit.MINUTES,new LinkedBlockingDeque<>(1000));
这样手动创建线程池的好处是可以限制阻塞队列的大小以及对一些参数的设置,下面解释下具体的参数。
- corePoolSize:核心池大小,表明主要处理任务的线程池大小,对应第一个10
- maxPoolSize:线程池的最大线程数,表明在核心池满了以及阻塞队列满了之后,可以用多余的线程处理额外的任务,对应第二个10。
- TimeAlive:即非核心池线程在空闲时的存活时间,对应0
- unit:非核心线程存活的时间单位,对应TimeUnit.MINUTES
- 阻塞队列大小,对应最后一个参数
手动创建线程池可以避免无限大的阻塞队列引起的OOM。
下面讲解下执行过程,代码如下
/**
* Executes the given task sometime in the future. The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.
*
* @param command the task to execute
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
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);
}
根据上面的代码可以看出分为三个步骤
- 刚开始初始化时,线程数小于核心池大小,则用核心线程去处理任务。
- 如果核心池线程已经满了,即都在处理任务,则任务进入阻塞队列
- 如果阻塞队列满了,则用maxPoolsize的线程处理任务
- 如果maxPoolSize的线程仍然满了,则拒绝任务。
另外在看代码时发现了一个很有意思的现象,即源码通过一个变量来维护线程池的状态以及线程池当前的线程数。