使用ThreadGroup监控线程退出

问题

做性能优化时,有这样一个需求:将运行时间短、申请内存多、结果不留在内存中的操作放到临时进程,运行完后杀掉这个进程,达到迅速降低内存占用的目的。

这个需求的难点在于:

  1. 临时任务在服务里运行,实测发现所有任务线程退出后,进程也不退出。进程中还有main, binder0, binder1等线程在运行
  2. 放到临时进程的的代码里会起新的线程,有的是第三方SDK里new出来的,无法修改。怎样监控这些线程成了问题

ThreadGroup

联想到操作系统中的父子进程,如果在一个线程中new 出现的新线程是是当前线程的子线程就好了。Google线程子线程无果。查看Thread的文档发现了下面的构造函数,可以指定线程所属的ThreadGroup

1
2
3
4
Thread(ThreadGroup group, Runnable runnable)
Thread(ThreadGroup group, Runnable runnable, String threadName)
Thread(ThreadGroup group, String threadName)
Thread(ThreadGroup group, Runnable runnable, String threadName, long stackSize)

再去看ThreadGroup的文档,哈,正是我们需要的。ThreadGroup有以下特性:

  1. 每个线程都属于一个ThreadGroup,如果构造函数没有指定,则与当前线程的ThreadGroup相同
  2. ThreadGroup有层级结构,构造函数可以指定父ThreadGroup
  3. 通过ThreadGroup可以获得属于这个ThreadGroup的线程

基于以上特点,我们就可以基于ThreadGroup创造出线程的层级结构来。

另外,Thread.getAllStackTraces()内部就是使用ThreadGroup.enumerate获得所有正在运行的线程的。

虽然Android的ThreadGroup的文档里说,这个类obsolete了,并给出了Effective Java上对应的Item,书上给出的理由是:

  1. 这个类没有设计好,是个半成品
  2. 这个类的大部分功能,已经被其他类替代了

对于我们的场景来说,这两个理由并不充分,而且没有更好的办法,用一下也无妨。

解决方案

  1. 使用ThreadPoolExecutor(链接)来运行任务,定制ThreadPoolExecutorThreadFactory,给ThreadFactory产生的线程设置相同的ThreadGroup(记为rootThreadGroup)
  2. 线程池设置 allowCoreThreadTimeOut(true),以便一定时间没有新任务进来,线程池的线程都退出,防止干扰检测其他线程
  3. 重写ThreadPoolExecutorbeforeExecuteafterExecute,统计运行完成的任务数
  4. 所有任务完成后,监控线程恢复运行,定时检测rootThreadGroup中的线程个数。当个数为0或者超时时间到了,就杀进程,并上报还没退出的线程信息
  5. 监控线程设置为Daemon,有任务运行时暂停,线程池中的线程超时退出后开始检测。有新任务来了,停止本次检测
  6. 为了解决有些任务添加进来后需要立即开始执行(如需要和用户交互的),将任务分为两类:常规任务和紧急任务,放在不同的线程池中运行:
    1. 常规线程池的参数:new ThreadPoolExecutor(threadCount, threadCount, EXIT_DELAY_MILLI, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(Integer.MAX_VALUE), threadFactory),最大线程数与CPU核数有关
    2. 紧急线程池的参数:new ThreadPoolExecutor(0, Integer.MAX_VALUE, _EXIT_DELAY_MILLI, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(), threadFactory);,新任务来了,立刻交给工作线程运行,如果没有空闲的工作线程,则创建一个新的

总结

ThreadGroup可以构建线程的层级结构,并管理其中的线程。平时我们写的代码,绝大部分都不会自定义ThreadGroup,因此可以用来监控一组相同来源的线程。