【内容摘要】
在java中,如果需要进行多线程编程,可以采用java自带的线程池来实现,线程池对于我们新手来说是一个非常好的选择,因为我们可以不用关心线程池中线程是如何调度的,避免在多线程编程过程产生死锁等问题。在了解线程池的使用前,本文首先介绍一下java线程池的内部原理。
【正文】
上一篇文档http://blog.csdn.net/zly_ir/article/details/78785237 在介绍了线程池中的几个重要的类过程中,提到了几个重要的方法,如execute()方法等,在本篇文章中,我们将详细介绍execute()方法是如何实现的,在介绍方法之前,我们首先回顾一下ThreadPoolExecutor中的几个重要参数,这些参数将在后续方法中经常出现:
//任务缓存队列,用来存放等待执行的任务
private final BlockingQueue<Runnable> workQueue;
//线程池的主要状态锁,对线程池状态(比如线程池大小 //runState等)的改变都要使用这个锁
private final ReentrantLock mainLock = new ReentrantLock();
//用来存放工作集
private final HashSet<Worker> workers = new HashSet<Worker>();
//线程存货时间
private volatile long keepAliveTime;
//是否允许为核心线程设置存活时间
private volatile boolean allowCoreThreadTimeOut;
//核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int corePoolSize;
//线程池最大能容忍的线程数
private volatile int maximumPoolSize;
//线程池中当前的线程数
private volatile int poolSize;
//任务拒绝策略
private volatile RejectedExecutionHandler handler;
//线程工厂,用来创建线程
private volatile ThreadFactory threadFactory;
//用来记录线程池中曾经出现过的最大线程数
private int largestPoolSize;
//用来记录已经执行完毕的任务个数
private long completedTaskCount;
1.execute()方法
在ThreadPoolExecutor类中,最核心的任务提交方法是execute()方法,虽然通过submit也可以提交任务,但是实际上submit方法里面最终调用的还是execute()方法,所以我们以execute()方法的实现原理为例进行说明:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//如果线程池中当前线程数不小于核心池大小
//或者addIfUnderCorePoolSize这个方法返回false
//则进入if语句执行接下去的代码
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
//如果当前线程池处于RUNNING状态,则将任务放入任务缓存队列;
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
//如果当前线程池不处于RUNNING状态或者任务放入缓存队列失败
//则执addIfUnderMaximumPoolSize(command)
else if (!addIfUnderMaximumPoolSize(command))
//如果执行addIfUnderMaximumPoolSize方法失败
//则执行reject()方法进行任务拒绝处理。
reject(command); // is shutdown or saturated
}
}
2.在execute()方法中,有两个重要的功能模块,addIfUnderCorePoolSize和addIfUnderMaximumPoolSize,下面介绍这两个功能的具体实现,并附上注释
(1)addIfUnderCorePoolSize
private boolean addIfUnderCorePoolSize(Runnable firstTask) {
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//判断当前线程池中的线程数目是否小于核心池大小;
//判断前首先获取锁,目的是为了防止在在execute方法判断的时候
//poolSize小于corePoolSize,而判断完之后,在其他线程中又向
//线程池提交了任务,就可能导致poolSize不小于corePoolSize了
if (poolSize < corePoolSize && runState == RUNNING)
//创建线程去执行firstTask任务
//传进去的参数为提交的任务,返回值为Thread类型
//具体实现下面会介绍
t = addThread(firstTask);
} finally {
mainLock.unlock();
}
//为空则表明创建线程失败
//(即poolSize>=corePoolSize或者runState不等于RUNNING)
if (t == null)
return false;
t.start();
return true;
}
在addIfUnderCorePoolSize方法中,有一个任务添加函数addThread,它的具体实现如下:
private Thread addThread(Runnable firstTask) {
//用提交的任务创建了一个Worker对象
Worker w = new Worker(firstTask);
//调用线程工厂threadFactory创建了一个新的线程t
Thread t = threadFactory.newThread(w);
if (t != null) {
//将线程t的引用赋值给了Worker对象的成员变量thread
w.thread = t;
//通过workers.add(w)将Worker对象添加到工作集当中
workers.add(w);
//当前线程数加1
int nt = ++poolSize;
if (nt > largestPoolSize)
largestPoolSize = nt;
}
return t;
}
addThread中的Worker类是一个实现了Runnable 的类,具体实现如下
private final class Worker implements Runnable {
private final ReentrantLock runLock = new ReentrantLock();
private Runnable firstTask;
volatile long completedTasks;
Thread thread;
Worker(Runnable firstTask) {
this.firstTask = firstTask;
}
boolean isActive() {
return runLock.isLocked();
}
void interruptIfIdle() {
final ReentrantLock runLock = this.runLock;
if (runLock.tryLock()) {
try {
if (thread != Thread.currentThread())
thread.interrupt();
} finally {
runLock.unlock();
}
}
}
void interruptNow() {
thread.interrupt();
}
private void runTask(Runnable task) {
final ReentrantLock runLock = this.runLock;
runLock.lock();
try {
if (runState < STOP &&
Thread.interrupted() &&
runState >= STOP)
boolean ran = false;
//beforeExecute方法是ThreadPoolExecutor类的一个方法,
//没有具体实现,用户可以根据自己需要重载这个方法和后面的
//afterExecute方法进行统计信息,比如某个任务的执行时间等
beforeExecute(thread, task);
try {
task.run();
ran = true;
afterExecute(task, null);
++completedTasks;
} catch (RuntimeException ex) {
if (!ran)
afterExecute(task, ex);
throw ex;
}
} finally {
runLock.unlock();
}
}
public void run() {
try {
//首先执行的是通过构造器传进来的任务firstTask
Runnable task = firstTask;
firstTask = null;
//不断通过getTask()去取新的任务来执行
//getTask是ThreadPoolExecutor类中的方法,并不是Worker类中的方法
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this); //当任务队列中没有任务时,进行清理工作
}
}
}
上述run()方法中的getTask是ThreadPoolExecutor类中的方法,他的具体实现如下:
Runnable getTask() {
for (;;) {
try {
int state = runState;
//判断当前线程池状态,如果runState大于SHUTDOWN
//(即为STOP或者TERMINATED),则直接返回null。
if (state > SHUTDOWN)
return null;
Runnable r;
//如果runState为SHUTDOWN或者RUNNING,则从任务缓存队列取任务。
if (state == SHUTDOWN)
r = workQueue.poll();
//如果线程数大于核心池大小或者允许为核心池线程设置空闲时间,
//则通过poll取任务,若等待一定的时间取不到任务,则返回null
else if (poolSize > corePoolSize || allowCoreThreadTimeOut)
r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
else
r = workQueue.take();
if (r != null)
return r;
//如果没取到任务,即r为null,则判断当前的worker是否可以退出
//workerCanExit() 方法将在之后介绍
if (workerCanExit()) {
if (runState >= SHUTDOWN)
interruptIdleWorkers(); //中断处于空闲状态的worker,该方法将在之后介绍
return null;
}
} catch (InterruptedException ie) {
// On interruption, re-check runState
}
}
}
在getTask()中,如果线程池处于STOP状态、或者任务队列已为空或者允许为核心池线程设置空闲存活时间并且线程数大于1时,允许worker退出。如果允许worker退出,则调用interruptIdleWorkers()中断处于空闲状态的worker,这里有两个重要函数,一个是workerCanExit(),另一个是interruptIdleWorkers(),下面分别介绍这两个函数的具体实现
private boolean workerCanExit() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
boolean canExit;
//如果runState大于等于STOP,或者任务缓存队列为空了
//或者 允许为核心池线程设置空闲存活时间并且线程池中的线程数目大于1
try {
canExit = runState >= STOP ||
workQueue.isEmpty() ||
(allowCoreThreadTimeOut &&
poolSize > Math.max(1, corePoolSize));
} finally {
mainLock.unlock();
}
return canExit;
}
这里有一个非常巧妙的设计方式,假如我们来设计线程池,可能会有一个任务分派线程,当发现有线程空闲时,就从任务缓存队列中取一个任务交给空闲线程执行。但是在这里,并没有采用这样的方式,因为这样会要额外地对任务分派线程进行管理,无形地会增加难度和复杂度,这里直接让执行完任务的线程去任务缓存队列里面取任务来执行。
(2)addIfUnderMaximumPoolSize,这个方法的实现思想和addIfUnderCorePoolSize方法的实现思想非常相似,唯一的区别在于addIfUnderMaximumPoolSize方法是在线程池中的线程数达到了核心池大小并且往任务队列中添加任务失败的情况下执行的:
private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (poolSize < maximumPoolSize && runState == RUNNING)
t = addThread(firstTask);
} finally {
mainLock.unlock();
}
if (t == null)
return false;
t.start();
return true;
}
其实它和addIfUnderCorePoolSize方法的实现基本一模一样,只是if语句判断条件中的poolSize < maximumPoolSize不同而已。
【附录】
线程池大小配置参考值
- 如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1
- 如果是IO密集型任务,参考值可以设置为2*NCPU