问题
做性能优化时,有这样一个需求:将运行时间短、申请内存多、结果不留在内存中的操作放到临时进程,运行完后杀掉这个进程,达到迅速降低内存占用的目的。
这个需求的难点在于:
- 临时任务在服务里运行,实测发现所有任务线程退出后,进程也不退出。进程中还有main, binder0, binder1等线程在运行
- 放到临时进程的的代码里会起新的线程,有的是第三方SDK里new出来的,无法修改。怎样监控这些线程成了问题
ThreadGroup
联想到操作系统中的父子进程,如果在一个线程中new 出现的新线程是是当前线程的子线程就好了。Google线程子线程无果。查看Thread的文档发现了下面的构造函数,可以指定线程所属的ThreadGroup
:
1 | Thread(ThreadGroup group, Runnable runnable) |
再去看ThreadGroup
的文档,哈,正是我们需要的。ThreadGroup
有以下特性:
- 每个线程都属于一个
ThreadGroup
,如果构造函数没有指定,则与当前线程的ThreadGroup
相同 ThreadGroup
有层级结构,构造函数可以指定父ThreadGroup- 通过
ThreadGroup
可以获得属于这个ThreadGroup的线程
基于以上特点,我们就可以基于ThreadGroup
创造出线程的层级结构来。
另外,Thread.getAllStackTraces()
内部就是使用ThreadGroup.enumerate
获得所有正在运行的线程的。
虽然Android的ThreadGroup的文档里说,这个类obsolete了,并给出了Effective Java上对应的Item,书上给出的理由是:
- 这个类没有设计好,是个半成品
- 这个类的大部分功能,已经被其他类替代了
对于我们的场景来说,这两个理由并不充分,而且没有更好的办法,用一下也无妨。
解决方案
- 使用
ThreadPoolExecutor
(链接)来运行任务,定制ThreadPoolExecutor
的ThreadFactory
,给ThreadFactory
产生的线程设置相同的ThreadGroup
(记为rootThreadGroup) - 线程池设置
allowCoreThreadTimeOut(true)
,以便一定时间没有新任务进来,线程池的线程都退出,防止干扰检测其他线程 - 重写
ThreadPoolExecutor
的beforeExecute
和afterExecute
,统计运行完成的任务数 - 所有任务完成后,监控线程恢复运行,定时检测rootThreadGroup中的线程个数。当个数为0或者超时时间到了,就杀进程,并上报还没退出的线程信息
- 监控线程设置为Daemon,有任务运行时暂停,线程池中的线程超时退出后开始检测。有新任务来了,停止本次检测
- 为了解决有些任务添加进来后需要立即开始执行(如需要和用户交互的),将任务分为两类:常规任务和紧急任务,放在不同的线程池中运行:
- 常规线程池的参数:
new ThreadPoolExecutor(threadCount, threadCount, EXIT_DELAY_MILLI, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(Integer.MAX_VALUE), threadFactory)
,最大线程数与CPU核数有关 - 紧急线程池的参数:
new ThreadPoolExecutor(0, Integer.MAX_VALUE, _EXIT_DELAY_MILLI, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(), threadFactory);
,新任务来了,立刻交给工作线程运行,如果没有空闲的工作线程,则创建一个新的
- 常规线程池的参数:
总结
ThreadGroup可以构建线程的层级结构,并管理其中的线程。平时我们写的代码,绝大部分都不会自定义ThreadGroup,因此可以用来监控一组相同来源的线程。