学习和使用Java的时间也不算短了,一直想找个机会好好学习线程池相关的技术细节,最近正好比较闲,因此抽空来把JDK 1.7线程池的实现给研究了一下。
线程池技术,不管是对于服务器端开发还是客户端开发都很重要。线程池最大的好处在于减少对象的创建和销毁带来的资源消耗。在面向对象程序编程中,创建和销毁对象是很消耗资源的。对于Java来说,更是如此,Java虚拟机会跟踪每一个对象,以便在对象被销毁后进行垃圾回收,这就是一些“池化技术”产生的原因。服务器端开发我不太熟悉,对于客户端开发来说。经常需要在非UI线程处理一些工作,如果每次都创建一个线程的话,对于CPU、内存、电池都受限制的手机来说,这显然是不可接受的。因此我们在进行Android开发时也采用线程池的技术来避免资源的消耗。由于我是Android程序猿,使用的语言是Java,因此我就使用JDK的线程池实现来讲解。
想要学习了解一个类,首先要从它的构造函数开始学习。首先我们来看一下它的一个常见的构造函数。
/** * @param corePoolSize 线程池中维护的目标线程个数,在运行一段时间以后线程个数与corePoolSize相等 * @param maximumPoolSize 线程池中中维护的最大线程个数 * @param keepAliveTime 当线程池中的线程个数大于corePoolSize,并且由有处于idle状态的线程时,最大的等待下一个任务的时间 * @param unit keepAliveTime的时间单元 * @param workQueue 保存要运行的Runnable对象的的队列 * @param threadFactory 当executor需要创建新的线程时使用的factory */
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
由构造函数可知,ThreadPoolExecutor可以创建一个线程池不断处理提交过来的Runnable对象,线程池在运行一段时间以后线程的个数等于corePoolSize,maximumPoolSize是线程池中最大的线程个数,当线程池中的线程个数大于corePoolSize并且由处于idle状态的线程,经过keepAliveTime之后线程会被销毁。线程池使用BlockingQueue来保存提交执行Runnable,当queue为空时,线程持续等待并被阻塞住。
接下来说一下ThreadPoolExecutor这个类中贯穿始终的一些变量和方法,这些正是ThreadPoolExecutor类设计的巧妙之处。ctl是一个atomic integer,它负责保存线程池的运行状态以及当前线程池的worker线程个数。整个线程池的最大容量是2^29 - 1,也就说ctl的低29位用来存储worker线程的个数,ctl的高三位,用来存储线程池的状态,如RUNNING、SHUTDOWN、STOP等等。源码中关于这么设计的解释是2^29 - 1也已经是一个非常大的数了,以后再扩展的话再换成atomic long也很方便,而且由于在ThreadPoolExecutor中需要频繁查询线程池状态以及worker线程个数,而移位运算效率又是相对比较高的,因此采用这样的方式来设计。废话少说,下面直接上源码。
//负责保存线程池状态以及worker线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
//worker线程数量最大值,2^29 - 1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
//线程池的各种运行状态
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
//取出ctl的高3位,获得线程池状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//取出ctl的低29位,获得worker线程个数
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
接下来是ThreadPoolExecutor的一些重要方法。
public void execute(Runnable command) {
//首先判空,空则抛异常
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//如果worker线程个数小于corePoolSize
if (workerCountOf(c) < corePoolSize) {
//调用addWorker方法,此方法稍后解释
//大致就是新增一个线程,并把command作为此线程的第一个任务
//如果新增并且执行成功,直接返回
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果worker线程个数 >= corePoolSize
//判断线程池运行状态,并且将command添加到保存任务的Queue中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//再次检查线程池状态,如果挂了,则从Queue中移除command,并且关闭线程池
if (! isRunning(recheck) && remove(command))
reject(command);
//否则线程池正常运行的话,如果worker线程的个数等于0,那么则新增线程
//这也就是我工作的项目中使用的线程池corePoolSize和maximumPoolSize相同的原因
//因为线程个数到达corePoolSize之后新增线程的条件很严苛,绝大多数情况下maximumPoolSize是无用的
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//当线程池的状态不是running
//或者虽然处于running,但是队列已经满了,拒绝添加新任务
else if (!addWorker(command, false))
reject(command);
}
对于execute方法的总结,(1)当线程池接收一个新的任务时,如果当前线程个数小于corePoolSize,那么直接调用addWorker方法新增线程,并且将command作为第一个任务进行执行。(2)如果当前线程个数大于等于corePoolSize,首先判断线程池运行状态,如果状态正常,然后试图将任务添加到队列中。如果线程池状态异常或者缓存队列已经满了,尝试新增worker线程,如果新增worker线程仍失败,则拒绝command。(3)线程个数大于等于corePoolSize,线程池状态正常,并且将任务成功添加到缓存队列中,那么再次检查线程池状态。如果线程池挂了,那么拒绝任务。否则再次检查状态仍正常的话,如果worker线程个数为0,那么新增worker线程。
/** * @param firstTask 新增线程执行的第一个runnable对象 * @paran core 是否以corePoolSize为线程个数限制 */
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//检查线程池状态
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
//状态异常则直接返回
return false;
for (;;) {
//检查worker线程数量
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
//状态异常则直接返回
return false;
//增加worker线程数量
if (compareAndIncrementWorkerCount(c))
break retry;
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 {
//创建新的worker对象
w = new Worker(firstTask);
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();
//往worker线程set添加对象
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//如果添加worker成功,执行runnable
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
addWorker方法总结:此方法主要是完成添加worker对象的工作,添加成功之后则执行 runnable,不过 runnable有可能为空。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//此方法的核心在于此,worker在addWorker 被创建之后,run方法直接被调用,而run方法又会调用至此方法。在此方法中,通过while 循环,不断调用getTask从blockingQueue中取出任务来执行,如果缓存队列为空,则线程被阻塞,直到有新的任务被放入缓存队列中
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
runWorker()的方法的核心在于通过while 循环,不断调用getTask从blockingQueue中取出任务来执行,如果缓存队列为空,则线程被阻塞,直到有新的任务被放入缓存队列中。
至此,线程池的简单学习笔记已经到此结束了,线程池对于移动端开发来说有着非常重要的意义,必须要掌握好!