通常,当需要同时处理的任务比较多时,为了避免为每个任务开一个线程(因为这样会导致频繁的线程开启和销毁,开销较大),采用线程池技术来进行线程资源的复用。
在应用中,我们通常使用Executors类提供的静态方法来使用线程池:
ExecutorService exec0 = Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
ExecutorService exec1 = Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池
ExecutorService exec2 = Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池
其中ExecutorService是一个接口,实现线程池的核心是一个ThreadPoolExecutor。将任务比如一个Runnable,或者一个Callable作为参数传入submit方法即完成任务向线程池的提交,将在线程池中自动执行该任务。返回参数可以是一个Future<?>对象。
那么线程池内部具体是怎么实现的,诸如ExecutorService接口和和TheadPoolExecutor又有什么关系呢?下面通过jdk中的源码来学习一下。
与ThreadPoolExecutor类相关的接口和类的关系
public class ThreadPoolExecutor extends AbstractExecutorService{...};
public abstract class AbstractExecutorService implements ExecutorService {...}
public interface ExecutorService extends Executor{...}
public interface Executor {
void execute(Runnable command);
}
可以清楚地看到它们之间的关系,Executor 接口只声明了一个方法,而ExecutorService接口在其基础上扩展出了一些方法:
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
其中的submit是我们比较关注的方法,也就是核心的提交任务的方法。
AbstractExecutorService则实现了上述方法,比如如下三个版本的submit方法实现:
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
可以看到,提交任务的方式可以是Runnable也可以是Callable,并且为了最终可以返回参数,同意采用了将task包装成RunnableFuture,该接口双重继承了Runnable接口和Future接口。然后就是execute方法的调用,execute方法的实现交给了继承AbstractExecutorService类的子类来完成,这里就是我们要学习的核心类:ThreadPoolExecutorService。
核心类ThreadPoolExecutorService
其核心的构造器接口如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
其中corePoolSize、maximumPoolSize分别表示核心池的大小,最大线程池的大小。keepAliveTime和空闲线程的存活时间有关,workQueue是一个阻塞队列,用于缓存任务,后面两个参数分别用于产生线程和拒绝服务的处理方式。
首先,我们想搞清楚当一个任务通过线程池submit后,是怎么被执行的而submit实际上也就是调用了execute方法,所以execute就是线程池最核心的部分。
这篇博客中介绍的比较清楚了,但是我看了下我jdk版本是1.8.0_40,发现execute的实现和文章中介绍的有差异,细看了一下基本实现思路应该还是一样的。
在我的jdk版本中,execute方法是这样的:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
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的大小,如果小于,则直接为新进的任务增加一个工作线程addWorker(command, true)
,并返回。否则尝试将任务添加到阻塞队列中,后面是检查一些异常情况,如是否外部将线程池停止isRunning(recheck)
等。最后还有拒绝服务的情况。
下面我们再来看addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
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;
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);
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);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
这个方法用于增加工作线程,所以传入的参数其中的任务叫firstTask
, 可以看到前面一部分还用到了跳转指令,retry点。for(;;){}一部分还没看太懂,大致可以看出是对非正常执行情况的处理,都是一些跳转或返回。
后面的try块是我们关注的重点,首先将firstTask包装成了一个Worker类,还获取了Worker的线程得到t,向workers这个HashSet中添加工作线程,最后调用t.start();
来启一个工作线程。
我们再来看Worker类:
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable{...}
再来看其核心run()方法,
public void run() {
runWorker(this);
}
调用了ThreadPoolExecutor的runWorker方法,
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
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);
}
}
到这个方法执行时,工作线程已经启动了,进入这个方法后就不断在任务缓冲队列中提取任务执行,执行的过程中对该Worker加锁,直到任务队列为空。
注意到最后的finally语句块中的processWorkerExit(w, completedAbruptly);
,查看源代码
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
可以看到,从workers中将移出该工作线程。还可以看到allowCoreThreadTimeOut参数对行为的影响:该参数为false,则对core threads在即使空闲的情况下也不会被杀死,如果为true,则空闲的线程在一定的timeout后将被系统回收。在程序中就是若为false,则min=corePoolSize,如果当前剩余的工作线程大于等于该值,则不需要添加idle线程,否则需要增加一个idle线程,因为刚刚kill了一个工作完成的线程。
另外,还有线程池的关闭,shutdown()和shutdownNow(),前者不会立即终止线程,而是等待任务缓冲队列中的的任务被完全执行后在结束,但是期间不再接受新的任务。后者则是立即终止所有的线程,并且清空缓冲队列,返回尚未执行的任务。
应用
一般使用时,我们不是直接使用ThreadPoolExecutor类,而是通过文章开头时介绍的Executors工具类的静态方法来获得该类的实例对象。这就避免了繁杂的参数配置可能导致的潜在错误。
如何配置线程池的大小问题
一个策略就是根据任务的类型来配置线程池的大小:
对CPU密集型任务,我们采用Ncpu+1,对IO密集型任务,我们采用2*Ncpu。