java在1.5之后,提供了线程池ThreadPoolExecutor和工具类Executors。
但是一般不建议用Executors,因为在 线程池里有一个workQueue(在使用newFixedThreadPool和newSingleThreadExecutor创建线程池时用的是LinkedBlockingQueue)队列,是用来存储等待线程的任务。在使用Executors创建线程时,没有指定workQueue的大小,而workQueue默认的大小是Integer.MAX_VALUE,这样当等待的任务过多时,就会导致oom.
写一个测试用例,用来了解下线程池是如何工作的
public void threadPool() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
List<Integer> list = new ArrayList<>();
long begin = System.currentTimeMillis();
for (int i=0;i<10000;i++){
executorService.execute(new Runnable() {
@Override
public void run() {
list.add(1);
}
});
}
executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
System.out.println(list.size());
System.out.println("threadPool 耗时:"+(System.currentTimeMillis()- begin));
}
结果,大家都能猜到
10000
threadPool 耗时:17
现在就来看下线程池是如何工作的,以及如何重复利用线程的。
一、看到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();
//在此做了一个double check,把任务添加到队列中,再做一次检查,线程池是否在运行中
if (! isRunning(recheck) && remove(command))
reject(command);
//用一个非核心的线程来执行
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//不满足以上条件,则拒绝执行
else if (!addWorker(command, false))
reject(command);
}
其实这个方法,我们只关注 addWorker(command, true) 和 addWorker(null, false),这两个方法就行
1)、addWorker(command, true) 这个方法就时新建用一个核心的线程来执行
2)、 addWorker(null, false) 此时已经把任务放在了任务队列中,起用一个非核心的线程来执行
3)、核心线程是当前线程池实际的数量小于设定的最小容量时 , 创建的。非核心线程是实际的线程数量大于最小容量,而小于最大的容量时,创建的。核心线程是可以重复使用的,非核心线程是空闲时间限定的。当空闲大于keepAliveTime时会被移除掉。
二、现在看下addWorker的内部
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
//自旋锁,用cas来完成线程池容量的加1操作
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
}
}
//创建一个Worker对象,来执行任务
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;
}
这个方法,我们需要关注下Worker,这个Worker也是实现了Runnable的。这时就会出现两个任务,一个是Worker,一个是实际要执行的任务(即我们当时传的任务,也就是此时的firstTask)。
我们关注下这个代码里的以下行
private boolean addWorker(Runnable firstTask, boolean core) {
//创建一个Worker对象,来执行任务
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
t.start();
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
实时就是创建一个Worker线程,然后调用start启动了。
看下worker的构造方法:
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
这个worker有一个线程属性,创建了一个线程,把worker自己当成Runnable传到了线程里,此时需要明确传入的是worker而不是firstTask.
接下来看下worker的run方法
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
此方法比较简单,就调用了runWorker,再看下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 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);
}
}
这个方法里,需要关注以下行
Runnable task = w.firstTask;
while (task != null || (task = getTask()) != null) {
task.run();
}
这个方法里,会执行firstTask,然后再循环去取task,如果能取到就再执行取到的task。这时getTask是从任务队列中取的。这就是线程重复使用的核心代码。
总结:我们可以看到线程池并不是创建一个线程,然后使用,使用完之后放回线程池,待后续任务继续使用。而是线程池里有自己的任务,这个任务可以循环去取任务列表里的任务,如果取不到,就会阻塞,一直等到有新的了任务了,再执行。在此过程中线程并不存在用完结束的情况。当然会有非核心的线程,这个线程在阻塞时会有超时时间,超时了,就会自己销毁。