线程池为什么要shutdown()
线程的内存泄漏
线程池在处理任务的时候,内部会启动线程,而线程的启动在addWorker中,也是使用Thread对象的start()方法。这种线程会占用一个虚拟机栈,在JVM层面输入GC root,根据可达性分析算法,这个线程就不会被回收,会一直占用JVM内存资源,这样就会造成所有的线程的核心线程永远都不会被回收,也就是内存泄漏。
另外,当执行任务时,start()会调用Worker的run方法(实现了Runnable),而在Worker的run方法中又使用了ThreadPoolExecutor的runWorker(Worker w)方法。也就是启动了的线程,还指向了Worker对象。Worker对象也无法被回收。
同时Worker是ThreadPoolExecutor的内部类,如果内部类不能被回收,外部类ThreadPoolExecutor也不能被回收。所以说,如果没有关闭ThreadPoolExecutor,就会造成堆内存占用很严重,进而影响GC。
调用了shutdown()
把线程池的状态从RUNNING改为SHUTDOWN,所有阻塞的线程,通过SHUTDOWN跳出任务获取的过程,因为我们核心线程执行完任务后,通过getTask()从阻塞队列中获取任务。如果是非核心线程,则调用poll(keepAliveTime,TimeUnit.NANOSECOND)来获取任务,一段时间后,获取不到线程结束。如果是核心线程,执行take(),一直在这里等待阻塞。当执行shutdown(),更改了线程池的状态后,就不会再走到take()方法了.
还会调用InterruptIdleWorkers(),把所有的没有执行中断任务的线程的interrupt标记设为true。设为中断后,这个时候已经走到take()方法的线程就会被唤醒,会继续向下走,重新循环,重新判断线程池是否已经shutdown(),进而退出getTask()方法,就会退出runWorker(Worker w);最后做一些处理,返回到Worker的run方法,结束run方法。
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
调用了shutdownNow()
把状态改为STOP.当执行到getTask()之前就会判断线程池的状态,线程池的状态不是RUNNING就直接结束,并且interruptWorkers()会中断所有正在进行的线程,包括正在进行的任务。以更快的速度结束掉线程的生命周期
结论
调用shutdownNow()或者shutdown()后,工作线程没有了,Woker对象就没有指向了,Worker对象没有指向了,线程池对象也就没有指向了,这样就可以被GC正常回收。