1.线程池处理任务流程:
ThreadPollExecutor执行execute()方法:
1)如果当前运行的线程少于corePollSize,则创建新的线程来执行任务(需要获取全局锁)
2)如果运行的线程数等于或者大于corePollSize,则将任务加入BlockingQueue。
3)如果BlockingQueue已满,则创建新的线程来处理任务(需要获取全局锁)
4)如果创建的新线程使得当前运行的线程数大于maximumPollSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。
源码实现:
//command 可以是普通的Runnable 实现类,也可以是 FutureTask
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 获取ctl最新值赋值给c,ctl :高3位 表示线程池状态,低位表示当前线程池线程数量
int c = ctl.get();
//workerCountOf(c) 获取出当前线程数量
//条件成立:表示当前线程数量小于核心线程数,此次提交任务,直接创建一个新的worker,对应线程池中多了一个新的线程。
if (workerCountOf(c) < corePoolSize) {
//addWorker 即为创建线程的过程,会创建worker对象,并且将command作为firstTask
//core == true 表示采用核心线程数量限制 false表示采用 maximumPoolSize
if (addWorker(command, true))
//创建成功后,直接返回。addWorker方法里面会启动新创建的worker,将firstTask执行。
return;
// 添加worker失败,可能存在并发
// 当前线程池状态发生了改变
// RUNNING SHUTDOWN STOP TIDYING TERMINATION
// 当线程池状态是非RUNNING状态时,addWorker(firstTask!=null, true|false) 一定会失败。
// SHUTDOWN 状态下,也有可能创建成功。前提 firstTask == null 而且当前 queue 不为空。特殊情况。
c = ctl.get();
}
// 1.当前线程数量已经达到corePoolSize
// 2.addWorker失败..
//条件成立:说明当前线程池处于running状态,则尝试将 task 放入到workQueue中。
if (isRunning(c) && workQueue.offer(command)) {
//再次获取ctl保存到recheck。
int recheck = ctl.get();
//条件一:! isRunning(recheck) 成立:说明你提交到队列之后,线程池状态被外部线程给修改 比如:shutdown() shutdownNow()
//这种情况 需要把刚刚提交的任务删除掉。
//条件二:remove(command) 有可能成功,也有可能失败
//成功:提交之后,线程池中的线程还未消费(处理)
//失败:提交之后,在shutdown() shutdownNow()之前,就被线程池中的线程 给处理。
if (! isRunning(recheck) && remove(command))
//提交之后线程池状态为 非running 且 任务出队成功,走个拒绝策略。
reject(command);
//1.当前线程池是running状态
//2.线程池状态是非running状态 但是remove提交的任务失败.
//保证线程池在running状态下,最起码得有一个线程在工作。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//1.入队失败
//2.当前线程池是非running状态
//1.入队失败.说明当前queue 满了!这个时候 如果当前线程数量尚未达到maximumPoolSize的话,会创建新的worker直接执行command
//2.线程池状态为非running状态,这个时候因为 command != null addWorker 一定是返回false。
else if (!addWorker(command, false))
reject(command);
}
工作线程:Worker:线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会获取工作队列里面的任务来执行。
//firstTask可以为null。为null 启动后会到queue中获取。
Worker(Runnable firstTask) {
//设置AQS独占模式为初始化中状态,这个时候 不能被抢占锁。
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
//使用线程工厂创建了一个线程,并且将当前worker 指定为 Runnable,也就是说当thread启动的时候,会以worker.run()为入口。
this.thread = getThreadFactory().newThread(this);
}
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);
}
}
线程池工作原理:
线程池中的线程执行任务分为两种情况:
(1)在execute()方法中创建一个线程时,会让这个线程执行当前任务
(2)这个线程执行完上图②的任务后,会反复从任务队列中获取任务来执行
2.线程池的使用
2.1 创建线程池:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
}
-
corePoolSize:核心线程数
- 核心线程会一直存活,即使没有任务需要执行
- 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
- 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
-
maximumPoolSize:最大线程数
- 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
- 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
-
keepAliveTime:线程空闲时间
- 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
- 如果allowCoreThreadTimeout=true,则会直到线程数量=0
-
rejectedExecutionHandler:任务拒绝处理器
- 当线程数已经达到maxPoolSize,任务队列已满,会拒绝新任务
- 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
-
ThreadPoolExecutor类有几个内部实现类来处理这类情况:
- AbortPolicy 该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
- CallerRunsPolicy 该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
- DiscardPolicy 忽视,什么都不会发生
- DiscardOldestPolicy 踢出进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
2.2.向线程池提交任务:
execute()和submit()
- execute()方法用于提交不需要返回值的任务,无法判断任务是否被线程池执行成功
- submit()方法用于提交需要返回值的任务。线程池会返回一个Future对象,通过Future对象可以判断任务是否执行成功,并且可以通过future对象的get方法得到返回值,get方法会阻塞当前线程直到任务完成。
2.3.关闭线程池
可以调用线程池的shutdown或者shutdownNow方法关闭线程池。原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法中断线程,所以无法响应中断的任务可能无法终止。
shutdown和showdownNow的区别:
- shutdownNOW首先将线程池的状态设置为STOP,然后尝试停止所有正在执行或者暂停的线程,并返回等待执行任务的列表
- shutdown只是将线程池状态设置为SHUTDOWN状态,然后中断所有没有正在执行任务的线程。