阅读前,先抱着问题,再来读,效果可能更好
- 池为什么可以帮我管理线程?
- 应该选择什么样的线程池?
1、线程池的好处
引用自《Java并发编程的艺术》第9章 Java中的线程池
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的损失。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性, 使用线程池可以进行统一的分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。
2、线程池的实现原理
线程处理/执行流程
部分引用自《Java并发编程的艺术》第9章 Java中的线程池
3、线程池的使用
3.1、线程池的创建
线程池类的结构
属性
// 1) 参见上图中状态分解
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
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;
//工作队列,不满足execut条件的Runnable进入等待执行的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//已在运行的线程,标记在HashSet中,用以监控线程
private final HashSet<Worker> workers = new HashSet<Worker>();
//重入锁,用于线程池对象并发被多线程调用
private final ReentrantLock mainLock = new ReentrantLock();
//条件对象,控制等待任务执行结束调用
private final Condition termination = mainLock.newCondition();
//拒绝策略
private volatile RejectedExecutionHandler handler;
//非核心线程的阻塞在poll方法上最长等待时间超时
private volatile long keepAliveTime;
//1.6引入的额外可控制核心线程是否可以超时,且超时时间为keepAliveTime
private volatile boolean allowCoreThreadTimeOut;
//当构造方法,没有指定拒绝策略,则默认拒绝策略为抛出异常
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
核心静态方法
// Packing and unpacking ctl
// 1) 参见上图中状态分解
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数介绍:
- corePoolSize:核心线程数,默认创建的线程数量,默认情况下即使空闲状态,也存活
- maximumPoolSize:最大线程数,超过核心线程数量的时候,会额外创建线程,但线程处在空闲状态时,就会死亡,有人将至称为扩展线程池,本质上还是在一个池中
- keepAliveTime:超过核心线程的,从任务队列中workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS),等待的最长时间,这样线程return就可以直接结束线程
- unit:枚举类型时间单位
- workQueue 指定工作队列,继承自阻塞队列的实现类
- threadFactory线程工厂,创建指定的统一样式的线程,默认是Executors.defaultThreadFactory()
- handler 拒绝策略,默认是中断策略AbortPolicy,抛出异常
3.2、向线程池提交任务
参照我的另一篇博客讲解FutureTask异步解耦线程和返回值https://blog.csdn.net/sawiii/article/details/102885586
submit(Runnable task)
//AbstractExecutorService中的方法
//Runnable任务无返回值,即返回值为null,一般常用的提交为Runnable,和Callable<T>
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);//调用newTaskFor
execute(ftask);
return ftask;
}
/**
* 封装为FutureTask实现类任务,实现自RunnableFuture(继承自Runnable和Future接口)
*/
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
//初始化返回值任务的状态
this.state = NEW; // ensure visibility of callable
}
submit(Runnable task, T result)
//runable线程本质是没有返回值的,可额外指定返回值result
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;
}
submit(Callable task)
//常用的提交方法,返回值为call方法的返回值
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
这里最重要的是ftask 可以直接返回由FutureTask控制,而线程的执行却是线程池管理,实现了解耦。
3.3、执行流程
- 如果运行的线程的数量线程核心池的数量,尝试新建一个线程作为Worker(核心线程)对象的第一个任务
- 如果任务可以成功入队,仍需二次检测我们是否线程是否应该入队,
- 可能自从我们第一次的检测后线程池被shutdown或shutdownNow,那么就需要移除线程并且拒绝执行。
- 可能加入之后之前检测的运行中的线程已经都执行完毕,且都退出了,那么此时workCount=0,那么需要向worker中添加一个null线程(null线程依托于worker的run方法),目前是让队列中的线程可以被poll或者take执行。
- 如果入队失败,尝试添加worker(非核心线程),添加失败(超出最大线程数量或线程池被关闭了),则拒绝任务。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {//判断work的数量,是否小于核心池中的数量
if (addWorker(command, true))//添加到works中添加成功则返回
return;
c = ctl.get();//添加失败,二次检测
}
if (isRunning(c) && workQueue.offer(command)) {//线程池是否运行,入等待队列成功
int recheck = ctl.get();//二次检测
if (! isRunning(recheck) && remove(command))//非RUNNING状态,出队
reject(command);//拒绝执行
else if (workerCountOf(recheck) == 0)//work数量为0
addWorker(null, false);//添加null线程,目前是让队列中的线程可以被执行。
}
else if (!addWorker(command, false))//非核心线程,添加至worker中
reject(command);//添加失败,则拒绝执行。
}
//添加worker
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 && //如果状态大于或等于SHUTDOWN,
! (rs == SHUTDOWN && //非状态等于SHUTDOWN或任务不为null或队列为空
firstTask == null &&
! workQueue.isEmpty()))
return false;//则不添加至Worker中
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY || ///判断worker数量是否已经大于极限最大数量,则不添加至Worker中
wc >= (core ? corePoolSize : maximumPoolSize))//work数量满足核心数量或者满足大于池最大数量,则不添加至Worker中
return false;
if (compareAndIncrementWorkerCount(c))//CASworker数量++,
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); //新建worker
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 ||//池状态是否小于SHUTDOWN
(rs == SHUTDOWN && firstTask == null)) {//池状态等于SHUTDOWN ,或首个任务为null
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);//添加至Set中
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;
}
// 调用start的时候自动runWorker执行的时候,调用
public void run() {
runWorker(this);
}
//run worker
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try { //重点在于getTask方法,不断从队列中取出任务。
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();//执行任务,通过FutureTask执行。
} 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);//每一个线程执行结束,均会判断任务池是否应该退出。
}
}
3.3、拒绝策略 实现了RejectedExecutionHandler接口
默认策略-AbortPolicy
中断并抛出异常
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
DiscardPolicy
丢弃任务
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
DiscardOldestPolicy
丢弃队列中的最先入队的任务
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
CallerRunsPolicy
主线程直接执行FutureTask的run方法
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
关闭线程池
4、钩子函数
子类继承ThreadPoolExecutor可实现此类钩子函数。
//在执行FutureTas.krun方法之前
protected void beforeExecute(Thread t, Runnable r) { }
//在执行FutureTas.krun方法之后
protected void afterExecute(Runnable r, Throwable t) { }
//调用shutdown方法
void onShutdown() {
}
//线程池在最终关闭进入TERMINATED状态之前,在TIDYING状态之后,调用该方法
protected void terminated() { }
官方给出的例子
class ExtendedExecutor extends ThreadPoolExecutor {
// ...
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t == null && r instanceof Future<?>) {
try {
Object result = ((Future<?>) r).get();
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null)
System.out.println(t);
}
}
5、线程池的关闭
参考上图
shutdown()
- 状态变更,状态由RUNNING->SHUTDOWN
- 拒绝接受新线程,但是会执行等待队列中的线
shutdownNow()
- 状态变更,状态由RUNNING->STOP
- 拒绝接受新线程,移除阻塞队列中的线程,并返回这些线程的集合
- 核心线程池中正在执行run方法的任务会继续执行下去,当执行完毕将从队列中take或者poll,此时可以响应中断并抛出异常
6、线程池的类型
Executors工具类中原生定义的线程池种类的使用,非深入。
6.1、newFixedThreadPool(int nThreads)
固定线程池,创建固定的大小的核心线程数量等于最大线程数量,限制池的最大数量。
6.2、newCachedThreadPool()
缓存线程池,核心线程为0,最大线程数量为Integer.MAX_VALUE,也就是说,所有添加进来的线程都是非核心线程,且超时时间为60秒,适用短时间内大量创建的线程短任务线程
(原理:Executors.newCachedThreadPool使用了该队列,原理是offer在开始是一定不会执行成功的,另一线程在poll的时候(此时SynchronousQueue存在等待结点),如果在指定超时时间,如果有另一线程尝试offer,则队列成功,并且将该元素给的正在poll的线程;
如果超时时间内没有任务加入,或者超时时间外才有任务加入,则poll的线程将会死亡,且加入的任务会主动创建一个线程start运行该任务)
(所以想重用线程,线程任务加入的时间和短时间的任务的耗时之间需平衡,否则无法达到重用的目的。)
(复用的本质是线程不会死亡,那么就不存在创建和销毁线程的时间同时也无需上下文的切换。)
6.3、newSingleThreadExecutor()
单核心线程池,单核心线程,当固定线程池数量等于1时,就是单核心线程池,适用于需要顺序地执行各个任务。
6.4、newScheduledThreadPool(int corePoolSize)
固定时延和周期性定时任务
提交方法:
schedule(Runnable...)
schedule(Callable...)
scheduleAtFixedRate(Runnable r,long initialDelay,long period,TimeUntil unit)
scheduleWithFixedDelay(Runnable r,long initialDelay,long delay,TimeUntil unit)
方法scheduledAtFixedRate和方法scheduledWithFixedDelay之间的联系与区别?
- scheduledAtFixedRate和scheduledWithFixedDelay在第一次执行都会延时initialDelay,但scheduledAtFixedRate的任务执行过程时间超过了间隔period,则下一次的任务将会直接执行,因为等待的时间超过了period;
- scheduledWithFixedDelay方法周期性执行任务,每一次执行前后间隔必定是delay,不论是否存在执行时间超过时延的情况。
例: scheduledAtFixedRate(c,0,3,second)
立即执行任务,任务间隔为3秒,如果某一次任务执行时间为4秒,那么下一次任务将会立即执行。因为已经过了3秒。 不会出现并行。
例:scheduledWithFixedDelay(c,0,3,second)
表示任务立即执行,且间隔时间为3秒。
即使某一次任务执行时间为4秒,下一次执行也是在上一次执行时间3秒后再执行。不会出现并行。
小结
- 学会配置线程池
- 知晓线程池复用的原理,本质是通过将任务交付给已创建在运行中的线程执行。
后续再行补充(不知道啥时候…),比如线程池的监控(也就是池暴露出来的public方法),JDK1.8引入的交叉加入线程池(充分利用CPU)
参考:
《Java并发编程的艺术》