Java虚拟机启动一个新线程的成本比较高,当程序中需要启动大量且生存期很短暂的线程时,可以考虑使用线程池。Java为我们提供了四种线程池使用。
1)创建单个线程的线程池
ExecutorService threadPool = Executors.newSingleThreadExecutor()
2)创建多个线程的线程池
ExecutorService threadPool = Executors.newFixdThreadPool(7)
3)创建可变线程数的线程池
ExecutorService threadPool = Executors.newCachedThreadPool()
4)创建可调度的线程池
ScheduledExecutorService threadPool =Executors.newScheduledThreadPool()
可以看到,四种线程池均是使用Exectors类创建,进入该类查看,创建各种类型的线程池最终是构造了ThreadPoolExecutor对象。只不过不同的线程池构造ThreadPoolExecutor参数不同。如下
1)newSingleThreadExecutor:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
2)newFixedThreadPool:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
3)newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
4)newScheduledThreadPool
ScheduledThreadPoolExecutor是继承了ThreadPoolExecutor
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
这里我们简单回忆下线程池的使用方法,外部通过调用sumbit(Runnable runnable)将runnable传递给线程池。如下:
public class ThreadPoolTest {
public static void main(String[] args) {
//创建一个具有固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(6);
Runn r1 = new Runn();
Runn r2 = new Runn();
pool.submit(r1);
pool.submit(r2);
pool.shutdown();
}
}
class Runn implements Runnable{
public void run() {
for(int i=0;i<30;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
ThreadPoolExecutor.sumbit()方法如下:
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
将外部传递过来的Runnable进行封装,调用execute()方法。那么我们就将该方法作为起点,分析线程池执行过程及原理。先上一张流程图:
当调用execute(),线程池首先会判断当前正在执行任务的线程数量是否已经超过了核心线程数,如果没超过,那么启动一个新线程并将execute()传入的runnable作为first task执行;若当前线程数已达到核心线程数,则将执行任务放入队列中等待执行。(线程池中正在跑着的线程,会首先执行first task,执行完后后不断从队列中取出执行任务执行,那这也达到了线程复用的目的。);若当前队列已满,则接着判断当前线程数量,是否已达到了线程池的最大线程数。若没达到。则创建普通线程执行。注意,这里所谓的核心线程和普通线程只是在创建时传入的一个布尔值。当然普通线程在执行完自己的first task也会不断从队列中读取任务执行。若当前线程数已达到最大线程数,则执行拒绝策略。下面我们看代码:
execute()
/**
* 将该Runnable任务加入线程池并在未来某个时刻执行
* 该任务可能执行在一个新的线程 或 一个已存在的线程池中的线程
* 如果该任务提交失败,可能是因为线程池已关闭,或者已达到线程池队列和线程数已满.
* 该Runnable将交给RejectedExecutionHandler处理,抛出RejectedExecutionException
*/
public void execute(Runnable command) {
if (command == null){
//如果没传入Runnable任务,则抛出空指针异常
throw new NullPointerException();
}
int c = ctl.get();
//当前线程数 小于 核心线程数
if (workerCountOf(c) < corePoolSize) {
//直接开启新的线程,并将Runnable传入作为第一个要执行的任务,成功返回true,否则返回false
//第二个参数true标明是核心线程
if (addWorker(command, true)){
return;
}
c = ctl.get();
}
//c < SHUTDOWN代表线程池处于RUNNING状态 + 将Runnable添加到任务队列,如果添加成功返回true失败返回false
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//成功加入队列后,再次检查是否需要添加新线程(因为已存在的线程可能在上次检查后销毁了,或者线程池在进入本方法后关闭了)
if (! isRunning(recheck) && remove(command)){
//如果线程池处于非RUNNING状态 并且 将该Runnable从任务队列中移除成功,则拒绝执行此任务
//交给RejectedExecutionHandler调用rejectedExecution方法,拒绝执行此任务
reject(command);
}else if (workerCountOf(recheck) == 0){
//如果线程池线程数量为0,则创建一条普通线程,去执行
//这里第二次参数false标明是普通线程
addWorker(null, false);
}
}else if (!addWorker(command, false))
//如果线程池处于非RUNNING状态 或 将Runnable添加到队列失败(队列已满导致),则执行默认的拒绝策略
reject(command);
}
我们可以看到,线程池把线程和所要执行的first runnable封装成了一个worker类,这里先简单的看一下,知道worker封装了thread和Runnable即可,细节我们后边讲解,如下:
Worker类:
private final class Worker extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
..................
接下来,我们就看核心方法addWorker()
/**
* 往线程池中添加Worker对象
* @param firstTask 线程中第一个要执行的任务
* @param core 是否为核心线程
* @return 添加是否成功
*/
private boolean addWorker(Runnable firstTask, boolean core) {
//这里有两层[死循环],外循环:不停的判断线程池的状态
retry: for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//一系列判断条件:线程池关闭,Runnable为空,队列为空,则直接return false,代表Runnable添加失败
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())){
return false;
}
//内循环:不停的检查线程容量
for (;;) {
int wc = workerCountOf(c);
//超过线程数限制,则return false
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)){
return false;
}
//添加线程成功,则直接跳出两层循环,继续往下执行.
//注意:这里只是把线程数成功添加到了AtomicInteger记录的线程池数量中,真正的Runnable添加,在下面的代码中进行
if (compareAndIncrementWorkerCount(c)){
break retry;
}
//再次判断线程池最新状态,如果状态改变了(内循环和外循环记录的状态不符),则重新开始外层死循环
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs){
continue retry;
}
}
}
//结束循环之后,开始真正的创建线程.
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//创建一个Worker对象,并将Runnable当做参数传入
w = new Worker(firstTask);
//从worker对象中取出线程
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
//拿到锁
mainLock.lock();
try {
//再次检查线程池最新状态
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
//检查准备执行Runnable的Thread的状态,如果该Thread已处于启动状态,则抛出状态异常(因为目前还没启动呢)
if (t.isAlive()){
throw new IllegalThreadStateException();
}
//将新创建的worker,添加到worker集合
workers.add(w);
...
workerAdded = true;
}
} finally {
//释放锁
mainLock.unlock();
}
if (workerAdded) {
//★Thread开始启动
t.start();
workerStarted = true;
}
}
} finally {
//添加worker失败
if (! workerStarted){
addWorkerFailed(w);
}
}
return workerStarted;
}
可以看到,如果添加worker成功,最终会调用worker.thread.start()来开启线程执行任务,那我们继续进入worker类,看看里面封装的thread执行的是什么Runnable
看看他的构造方法:
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
从线程工厂中获取线程,并将worker自己作为runnable传入线程,worker实现了runnable接口。也就是说addWorker()方法中调用worker.thread.start()方法最终会调用到worker复写的run()方法,worker.run()方法如下:
public void run() {
runWorker(this);
}
接着往里看runWorker(this)
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//取出Worker对象中的Runnable任务
Runnable task = w.firstTask;
boolean completedAbruptly = true;
...
try {
//注意这个while循环,在这里实现了 [线程复用]
//首先执行first task.run(),然后不断通过getTask()从队列里取任务
//直到队列中无任务,循环结束
while (task != null || (task = getTask()) != null) {
//上锁
w.lock();
//检查Thread状态的代码
...
try {
...
try {
//执行Worker中的Runnable任务
task.run();
} catch (...) {
...catch各种异常
}
} finally {
//置空任务(这样下次循环开始时,task依然为null,需要再通过getTask()取) + 记录该Worker完成任务数量 + 解锁
task = null;
w.completedTasks++;
w.unlock();
}
}
//该线程已经从队列中取不到任务了,改变标记
completedAbruptly = false;
} finally {
//线程移除
processWorkerExit(w, completedAbruptly);
}
}
也就是说只要队列中有任务,线程就会不断从其中取,直到队列中没有任务了。但是while循环不会结束。为什么呢?因为getTask()方法中是for死循环。所以被创建出来的线程会一直运行着,实现复用。该线程run()方法结束,该线程也就销毁了,随后调用processWokerExit()移除线程(更新降低线程数量)
分析到这里,我们就大概理解了线程池的工作原理,但一些细节,例如getTask()如何取任务?processWorkerExit()如何移除任务?拒绝策略又如何?这些细节,大家可以移步参考文章:点击打开链接 继续学习。