1. 创建线程池
建议使用ThreadPoolExecutor类来创建,而不是使用Executors中的静态方法来创建,创建出来的对象属于ExecutorService类型,可以执行各种多线程任务。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
2. 参数详解
按照参数顺序依次是:
- corePoolSize:核心线程数。当任务提交时,如果线程池中线程数少于核心线程数,就会创建新线程执行,即使有线程在闲置。,如果多于核心线程数,但少于最大线程数,只有所有线程都在运行才会创建新线程。
- maximumPoolSize:最大线程数,线程池允许存在的最大线程数
- keepAliveTime:最大空闲时间,当线程池中数量大于corePoolSize时,如果这时没有新任务提交,核心线程外的线程不会立即销毁,而是等待该最大空闲时间后销毁。
- unit:最大空间时间的单位。
- workQueue:用来保存等待被执行的任务的阻塞队列,任务必须实现Runable接口。Java中实现BlockingQueue的类有:
- ArrayBlockingQueue:Object[] 有界数组存放,takeIndex和putIndex表示取存索引,FIFO队列
- LinkedBlockingQuene和LinkedBlockingDeque:基于链表的阻塞队列,FIFO,吞吐量高于数组的
- SynchronousQuene:不能存储元素,一个线程的insert只能等待另一个线程remove之后执行;remove需要等待另一个insert;
- priorityBlockingQuene:优先队列,逻辑无界,受限于硬件,元素必须可以比较。无界的原理类似于ArrayList,对Object[]进行扩容。
- threadFactory:默认使用Executors.defaultThreadFactory()来创建线程,创建的线程具有相同的优先级,且是非守护线程,同时也设置了线程名称。
- handler:线程池饱和策略,当阻塞队列满了,且没有空闲线程,如果继续提交任务,如何处理多余任务,线程池提供了四种策略,在ThreadPoolExecutor有四个实现RejectedExecutionHandler接口的子类:
- AbortPolicy:默认策略,直接抛出异常
- CallerRunsPolicy:调用者线程来执行,如果线程池关闭,则丢弃
- DiscardOldestPolicy:丢弃最早的未执行任务,然后执行当前任务
- DiscardPolicy:直接丢弃策略
3. 线程池监控
public long getTaskCount() //线程池已执行与未执行的任务总数
public long getCompletedTaskCount() //已完成的任务数
public int getPoolSize() //线程池当前的线程数
public int getActiveCount() //线程池中正在执行任务的线程数量
4. 线程池原理
执行原理
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(); // ctl有线程池状态,和活动线程数
if (workerCountOf(c) < corePoolSize) { // 工作线程数小于核心数时
if (addWorker(command, true)) // true表示使用coreSize,false表示使用maxSize判断
return;
c = ctl.get(); // 失败重新获取ctl
}
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);
}
简单来说,在执行execute()方法时如果状态一直是RUNNING时,的执行过程如下:
- 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务;
- 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中;
- 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务;
- 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
这里要注意一下addWorker(null, false);,也就是创建一个线程,但并没有传入任务,因为任务已经被添加到workQueue中了,所以worker在执行的时候,会直接从workQueue中获取任务。所以,在workerCountOf(recheck) == 0时执行addWorker(null, false);也是为了保证线程池在RUNNING状态下必须要有一个线程来执行任务。
addWorker方法
addWorker方法的主要工作是在线程池中创建一个新的线程并执行,firstTask参数 用于指定新增的线程执行的第一个任务,core参数为true表示在新增线程时会判断当前活动线程数是否少于corePoolSize,false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize,代码如下:
// 根据运行状态和界限(根据core选择coreSize或maxSize),来选择是否增加worker执行任务
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 如果rs关闭,不再接收任务,且需要满足(队列为空,或者任务为null),都不增加worker
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry; // 尝试增加worker数,如果成功,跳出最外层for循环
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask); // 新建一个worker,每一个worker工作在一个线程上
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock(); // 使用可重入锁,
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
// 如果是运行状态,或者关闭状态但是还要执行队列里的任务,
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w); // 线程其实是一个HashSet集合
int s = workers.size();
if (s > largestPoolSize) // largesPoolSize保存线程池中出现过的最大线程数量
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); // 启动线程
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Worker类
线程池中的每一个线程被封装成一个Worker对象,ThreadPool维护的其实就是一组Worker对象,(使用HashSet数据结构保存)请参见JDK源码。
Worker类继承了AQS,并实现了Runnable接口,注意其中的firstTask和thread属性:firstTask用它来保存传入的任务;thread是在调用构造方法时通过ThreadFactory来创建的线程,是用来处理任务的线程。
Worker继承了AQS,使用AQS来实现独占锁的功能。
在Worker类中的run方法调用了runWorker方法来执行任务,runWorker是线程池类的方法,通过while循环不断通过getTask()方法来获取任务,调用task.run()方法来执行任务。