ThreadPoolExecutor原理详细解读二
构造器
其实构造器已经没什么好讲了,知道那些参数就可以了,其实都是调用第三个的,基本能自定义的都可以,比如队列workQueue,线程工厂threadFactory,拒绝策略defaultHandler:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
关闭shutdown
这个还是比较优雅的方式,会把所有空闲的线程关闭,然后尝试终止:
public void shutdown() {//会运行完现有的任务和队列列的任务,不会接受新任务了
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();//做线程的安全验证,不安全会报异常
advanceRunState(SHUTDOWN);//修改状态
interruptIdleWorkers();//中断所有空闲线程
onShutdown(); // hook for ScheduledThreadPoolExecutor //钩子函数
} finally {
mainLock.unlock();
}
tryTerminate();
}
修改状态advanceRunState
如果当前状态>=targetState状态,就直接跳出循环了,否则就自旋修改成targetState状态
private void advanceRunState(int targetState) {
// assert targetState == SHUTDOWN || targetState == STOP;
for (;;) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) || //已经是TIDYING or TERMINATED 就跳出循环
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))//修改成功调出循环,否则循环到成功为止
break;
}
}
中断空闲的线程interruptIdleWorkers
可以看到,他会去中断所有空闲的线程,因此其他线程还是会去队列中获取任务执行,直到队列空为止,即可以把任务都完成了然后终止:
private void interruptIdleWorkers() {
interruptIdleWorkers(false);//中断所有空闲的
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();//这里的锁是为了确保不让其他线程对workers有所操作
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {//没有中断过,且可以获得锁,也就是空闲了,不空的不能能获得锁的
try {
t.interrupt();//中断线程
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)//如果只是中断一个的话就退出
break;
}
} finally {
mainLock.unlock();
}
}
停止shutdownNow
这个还是比较粗暴的方式,会把所线程中断,把队列清空,然后尝试终止:
public List<Runnable> shutdownNow() {//中断所有线程,清空列表
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();//中断所有线程,不管是否空闲
tasks = drainQueue();//把队列中的任务返回并清空队列
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
中断所有线程interruptWorkers
活尝试去中断每一个线程:
private void interruptWorkers() {
// assert mainLock.isHeldByCurrentThread();
for (Worker w : workers)
w.interruptIfStarted();
}
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
清空队列并返回任务drainQueue
这个就是把workQueue中的任务移除,然后返回,其实就是把没做完的任务给返回来:
private List<Runnable> drainQueue() {//清空队列
BlockingQueue<Runnable> q = workQueue;
ArrayList<Runnable> taskList = new ArrayList<>();
q.drainTo(taskList);//将全部元素移除,并给taskList,这个操作相对高效
if (!q.isEmpty()) {//如果是DelayQueue或者其他的类型队列可能会一下子没清干净
for (Runnable r : q.toArray(new Runnable[0])) {//将队列里元素全都转为数组
if (q.remove(r))//从队列里移除任务
taskList.add(r);//将任务加入taskList
}
}
return taskList;
}
超时等待终止awaitTermination(long timeout, TimeUnit unit)
这个就是用了条件终止,会进行阻塞等待终止,如果超时就返回false:
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
while (runStateLessThan(ctl.get(), TERMINATED)) {
if (nanos <= 0L)
return false;
nanos = termination.awaitNanos(nanos);//超时条件同步
}
return true;
} finally {
mainLock.unlock();
}
}
设置核心线程数setCorePoolSize
重新设置核心线程数还可能需要进行空闲线程的中断,如果发现设置的核心线程数变多了,如果发现等待队列里有任务,还需要增加核心线程,否则就不增加核心线程了:
public void setCorePoolSize(int corePoolSize) {
if (corePoolSize < 0 || maximumPoolSize < corePoolSize)
throw new IllegalArgumentException();//小于0,或者比max大,就非法异常
int delta = corePoolSize - this.corePoolSize;//核心线程数量差
this.corePoolSize = corePoolSize;
if (workerCountOf(ctl.get()) > corePoolSize)
interruptIdleWorkers();//如果发现现有的线程个数大于核心线程数,就中断空闲的线程
else if (delta > 0) {//如果新的核心线程数多余老的
int k = Math.min(delta, workQueue.size());//从核心线程数和等待任务数中取最小的 作为k 下面要增加核心线程数了
while (k-- > 0 && addWorker(null, true)) {//如果k>0,且新开启线程成功,就循环开启,直到k为0。要么就是任务没了退出,要么线程数已经扩充到最新的核心线程数了
if (workQueue.isEmpty())//如果任务为空,那就跳出
break;//其实就是创建不超过核心线程数的线程来解决队列里的任务,如果队列里空了,就不增加线程了
}
}
}
预启动核心线程prestartCoreThread
预启动一个核心线程,不做任何事,等于预加载:
public boolean prestartCoreThread() {
return workerCountOf(ctl.get()) < corePoolSize &&//线程数小于核心线程数
addWorker(null, true);//且增加核心线程成功
}
确保预启动成功ensurePrestart
怕没启动成功,如果核心线程启动失败,就启动非核心线程:
void ensurePrestart() {//确保有一个线程启动
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);//没超过核心线程数
else if (wc == 0)
addWorker(null, false);//启动一个非核心的
}
预启动所有核心线程prestartAllCoreThreads
只要能创建的,都创建了:
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))//启动所有的核心线程,没任务也没关系,这样可以提高效率,不至于来了再创建线程
++n;
return n;
}
允许核心线程超时allowCoreThreadTimeOut
核心线程也是可以设置超时的,如果从false设置成了true,会中断空闲线程:
public void allowCoreThreadTimeOut(boolean value) {
if (value && keepAliveTime <= 0)//如果keepAliveTime<=0,且核心线程要超时回收就报异常
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
if (value != allowCoreThreadTimeOut) {//设置标志
allowCoreThreadTimeOut = value;
if (value)
interruptIdleWorkers();//如果是要收回,立即中断空闲线程
}
}
设置最大线程数setMaximumPoolSize
修改最大线程数,如果小于原来的,就要释放空闲线程了:
public void setMaximumPoolSize(int maximumPoolSize) {
if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)
throw new IllegalArgumentException();//不合理的抛出异常
this.maximumPoolSize = maximumPoolSize;
if (workerCountOf(ctl.get()) > maximumPoolSize)
interruptIdleWorkers();//如果当前线程数量已经大于最大的了,就中断空闲的
}
设置活跃时间setKeepAliveTime
如果设置的时间已经过期的话,就直接中断空闲的了:
public void setKeepAliveTime(long time, TimeUnit unit) {
if (time < 0)
throw new IllegalArgumentException();
if (time == 0 && allowsCoreThreadTimeOut())//核心线程不能设置0过期
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
long keepAliveTime = unit.toNanos(time);
long delta = keepAliveTime - this.keepAliveTime;
this.keepAliveTime = keepAliveTime;
if (delta < 0)//已经过期了就中断空闲线程
interruptIdleWorkers();
}
从队列里删除任务remove
可见,如果有任务删除,就会去尝试终止,毕竟可能没任务了,所以要尝试下:
public boolean remove(Runnable task) {
boolean removed = workQueue.remove(task);
tryTerminate(); // In case SHUTDOWN and now empty
return removed;
}
清除所有取消的任务purge
可以清除workQueue里所有取消的任务,如果发现异常就把剩下的转成数组,一个个清除:
public void purge() {//清除所有取消的任务
final BlockingQueue<Runnable> q = workQueue;
try {
Iterator<Runnable> it = q.iterator();
while (it.hasNext()) {
Runnable r = it.next();
if (r instanceof Future<?> && ((Future<?>)r).isCancelled())
it.remove();//如果设置了取消就删除
}
} catch (ConcurrentModificationException fallThrough) {
// Take slow path if we encounter interference during traversal.
// Make copy for traversal and call remove for cancelled entries.
// The slow path is more likely to be O(N*N).
for (Object r : q.toArray())//如果遇到异常的话,就把剩下来的队伍转到成数组,一个个移除
if (r instanceof Future<?> && ((Future<?>)r).isCancelled())
q.remove(r);
}
tryTerminate(); // In case SHUTDOWN and now empty
}
疑惑解答
基本的方法基本都分析过了,再说几个比较疑惑的点。
tryTerminate到处有
我们可以看到tryTerminate很多地方都有,为什么会这样呢:

我们可以从调用这个方法的地方看到,基本都是一些跟workQueue删除相关的,因为可能删除后,队列里没任务了,所以这个时候可以尝试去终止。
mainLock到处有
我们发现这个mainLock也很多地方有,这个是一个ReentrantLock类型的锁:

从上面的方法里可以分析出,这个锁主要是对ctl,workers,largestPoolSize,completedTaskCount等一些共享变量的操作进行互斥。另外就是对一些中断操作进行同步。
为什么用阻塞队列放任务
因为我的线程是要不停的执行任务,如果任务获取为空就停止了:

这样会导致一段时间内如果任务为空,大部分线程被回收,后面又来了高并发,又创建很多,这样对性能消耗比较大,所以应该是用阻塞的比较好,反正没有任务就阻塞好了,总比浪费CPU或者频繁创建消耗好,而且稳定。
为什么没用到synchronized
因为如果出现很多线程竞争资源的话synchronized可能会变成重量级锁,效率就低了,而用ReentrantLock来控制比较灵活而且效率相对高一点,还能超时中断。
为什么线程是线程工程创建的
可以进行进行自定义,而且让线程池的更加专注于管理和调度线程,不用管他的创建。
shutdownNow到底怎么中断的
我们可以看到如果调用shutdownNow,他会让线程中断:

但是这个好像没地方看到有中断响应啊,这个只是设置了标志位,线程还是会运行啊。确实是这样,如果没有对任务进行中断处理,就等于没中断啦,比如:
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
while (true)
System.out.println("完成");
});
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdownNow();
}
你会发现这样没用,他还是会无限输出,就跟shutdown()一样啦,因为没有响应中断啊。
所以你要这样在任务里处理中断:
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("完成");
}
});
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdownNow();
}
有中断就立即停掉了。当然也可以跑出中断异常,比如你用sleep。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。
本文全面解析了ThreadPoolExecutor的工作原理,包括构造器、关闭、状态修改、线程中断、任务队列管理等关键方法。深入探讨了核心线程数、最大线程数、活跃时间的设置与调整,以及如何优雅地关闭线程池。
632

被折叠的 条评论
为什么被折叠?



