一、引子
现在的业务开发中用到了ReactNative,热更新的代码下发使用的是GeckoClient框架。之前开发同学的实现是在Rn的基类Fragment和Activity中,创建一个GeckoClient成员变量,然后进行更新。这样,每个Rn的Fragment和Activity都有一个自己的GeckoClient实例,而GeckoClient中又有一个执行异步任务的线程池变量,而Rn的Fragment和Activity退出时,也没有调用线程池的shutdown()或shutdownNow()方法,那么就产生了两个疑问,这个线程池中的线程都会被关闭吗?这个线程池对象会被回收吗?
简化来讲:Android中,如果对象A被回收,某线程池对象的引用只被对象A持有,一直没有调用该线程池的shutdown()或shutdownNow()方法,那么该线程池中的线程都会被关闭吗?线程池对象会被回收吗?假定线程池中任务都执行完了,且不再有新的任务提交给它。
先说结论:
线程池对象能被回收,线程是否被关闭取决于这个线程池中核心线程池的数目和ThreadPoolExecutor对象中allowCoreThreadTimeOut的值。
二、线程相关分析
2.1 从GeckoClient的线程池入手
GeckoClient中声明的线程池代码如下:
private ExecutorService mThreadPool = Executors.newScheduledThreadPool(3);
生成的线程池类型为SheduledExcutorService,
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
进到ScheduledExcutorService的构造函数里
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
直接调用的是它的父类ThreadPoolExecutor的构造函数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
又调用了ThreadPoolExecutor同名的构造函数
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;
}
它有一个corePoolSize(核心线程数)的参数,说明如下:
corePoolSize:
int: the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set
线程池中最大允许的线程数目,即使它们是空闲的,除非allowCoreThreadTimeOut被设置成true!
看下allowCoreThreadTimeOut
private volatile boolean allowCoreThreadTimeOut;
If false (default), core threads stay alive even when idle. If true, core threads use keepAliveTime to time out waiting for work.
allowCoreThreadTimeOut默认值为false,此时,即使核心线程处于idle状态,也会保持存活。如果设置为true,那么核心线程将在空闲keepAliveTime时长后退出。
在ThreadPoolExecutor代码中查找allowCoreThreadTimeOut使用到的地方,共有三处,一处设置,两处使用。
2.2设置allowCoreThreadTimeOut值
public void allowCoreThreadTimeOut(boolean value) {
if (value && keepAliveTime <= 0)
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
if (value != allowCoreThreadTimeOut) {
allowCoreThreadTimeOut = value;
if (value)
interruptIdleWorkers();
}
}
Sets the policy governing whether core threads may time out and terminate if no tasks arrive within the keep-alive time, being replaced if needed when new tasks arrive. When false, core threads are never terminated due to lack of incoming tasks. When true, the same keep-alive policy applying to non-core threads applies also to core threads. To avoid continual thread replacement, the keep-alive time must be greater than zero when setting true. This method should in general be called before the pool is actively used.
设置在超时时间内没有新任务到达时管理核心线程超时和销毁的策略,如果核心线程关闭后,有新的任务到来,则开启新的线程来执行。如果为false,即使没有新的任务,核心线程也不会被销毁。如果为true,适用于非核心线程的存活时间也将应用于核心线程上。为了避免核心线程被频繁创新销毁,在allowCoreThreadTimeOut值为true时,keep-alive的值必须大于0。这个方法一般应该在线程池被使用前就调用。
代码比说明要容易读,如果值为true,且keepAliveTime小于等于0,抛出异常;如果值不同,则更新,更新值为true时,停止空闲线程的等待。
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
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();
}
}
这里t.interrupt() interrupt的是什么,在之后的介绍可以看到。
2.3 allowCoreThreadTimeOut的使用
allowCoreThreadTimeOut的使用有两个地方,都是和任务的运行有关
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 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();
} 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); ②
}
}
① getTask()
private Runnable getTask() {
for (;;) {
// 省略无关代码
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 如果该线程需要使用keepAliveTime控制
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;
}
}
}
其中workQueue是BlockingQueue类型的变量,用来存储线程池需要执行的Runnable,
private final BlockingQueue<Runnable> workQueue;
这里用到它的两个取head的方法:
(1)poll()方法
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
Retrieves and removes the head of this queue, waiting up to the specified wait time if necessary for an element to become available.
(2)take()方法
E take() throws InterruptedException;
Retrieves and removes the head of this queue, waiting if necessary until an element becomes available.
这里用以下一句话,就实现了默认情况下核心线程和非核心线程的存活控制。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
如果timed为true,则最多等待keepAliveTime时长;否则,一直等待。2.2中的interrupt()方法就是interrupt这里。
② processWorkerExit()方法
需要注意,从runWorker()方法和上面的理解可以看到processWorkExit()方法是在while循环结束后才得到执行的,因此,该线程执行完processWorkerExit()就要退出了。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// ...省略无关代码
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);// 尝试创建一个新线程
}
}
可以看到,如果allowCoreThreadTimeOut,线程直接退出;否则,如果没有corePoolSize个线程,则尽自己最后一份力,尝试创建一个线程。
2.4 总结
如果核心线程数为0,或者核心线程数大于0且allowCoreThreadTimeOut为true,如果超过keepAliveTimeout时长没有新任务到来的线程会被销毁;如果核心线程数大于0,且allowCoreThreadTimeOut为false,即使一直没有新任务到来,核心线程也不会被销毁。
从所在项目组的代码运行情况看,反复进入Rn页面,退出后,出现很多处于wait状态都geco-client的线程。
三、线程池内存分析
这里比较好分析,就不贴代码了。如果没有对线程池的强引用了,且任务都执行完成后(非必须,取决于Runnable是否直接或间接持有线程池的对象),线程池的对象就被回收了。