我创建了一个线程,然后创建一个ThreadPoolExecutor
并向其提交一些长时间运行的任务。在某些时候,原始线程由于未处理的异常/错误而死亡。执行程序应该发生什么(它是那个死线程的本地,没有外部引用它)?是否应该是GC?
编辑:这个问题从一开始就错误地制定了,但我会留下它,因为格雷提供了TPE如何工作的一些很好的细节。
线程被称为GC roots。这意味着无法收集正在运行(或未启动)的线程。它还意味着无法收集从这些线程引用的对象,这就是为什么你可以执行像new Thread(new MyRunnable()).start()
这样的事情,或者让线程池在没有任何引用的情况下运行。
如果线程是守护程序,则如果所有其他非守护程序线程都已停止,则它可以自动停止。您可以使用带有守护程序线程的线程池,但最好的方法是确保正确清理事物(即异常不应该杀死最终应该停止并清理线程池的线程)。
执行者会发生什么(它是死线程的本地,没有外部引用)?是否应该是GC?
答案比“是的,如果没有参考”那么复杂。这取决于在ThreadPoolExecutor
中运行的线程是否仍在运行。这又取决于创建了什么类型的TPE以及提交给它的“长时间运行的任务”是否已经完成。
例如,如果任务尚未完成,则线程仍将运行。即使它们已经完成,如果你有一个没有设置allowCoreThreadTimeOut(true)
的核心线程的TPE,那么线程也不会停止。 JVM从不垃圾收集正在运行的线程,因为它们被认为是GC "roots":
根据定义,运行线程对GC免疫。 GC通过扫描“根”开始工作,这些“根”被认为总是可以到达的;根包括全局变量(Java-talk中的“静态字段”)以及所有正在运行的线程的堆栈......
所以下一个问题是线程是否有引用回ThreadPoolExecutor
,我相信它们会。 Worker
内部类是Runnable
,存储在thread.target
并由Thread
执行,所以它不能GC'd。 Worker
不是static
所以它隐含了对外部ThreadPoolExecutor
实例的引用。 run()
方法实际上调用ThreadPoolExecutor.runWorker()
方法,该方法引用由ThreadPoolExecutor
管理的所有任务队列。因此,正在运行的线程将引用保存回Worker
和TPE,因此垃圾收集器无法收集TPE。
例如,这是一个引用TPE的运行池线程的典型堆栈帧:
java.lang.Thread.sleep(Native Method)
com.j256.GcTester$1.run(GcTesteri.java:15)
java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
java.util.concurrent.FutureTask.run(FutureTask.java:266)
>> java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
>> java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
java.lang.Thread.run(Thread.java:748)
但是,如果线程池任务已经完成并且它有0个核心线程或者核心线程超时,那么将没有与Worker
关联的ThreadPoolExecutor
线程。然后TPE将被垃圾收集,因为除了循环的之外没有对它的引用,GC足够智能地检测。
这是一个演示它的小样本测试程序。如果有1个核心线程,那么即使在注意到finalize()
文件存在之后工作线程退出之后,TPE也永远不会被关闭(通过/tmp/x
)。即使主线程没有引用它也是如此。但是,如果在线程超时之后有0个核心线程(在完成最后一个任务后1秒后),将收集TPE。
public class GcTester {
public static void main(String[] args) {
int numCore = 1; // set to 0 to have it GC'd once /tmp/x file exists
ExecutorService pool =
new ThreadPoolExecutor(numCore, Integer.MAX_VALUE,
1, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()) {
protected void terminated() {
System.out.println(this + " terminated");
}
};
pool.submit(new Runnable() {
public void run() {
while (true) {
Thread.sleep(100); // need to handle exception here
if (new File("/tmp/x").exists()) {
System.out.println("thread exiting");
return;
}
}
}
});
pool = null; // allows it to be gc-able
while (true) {
Thread.sleep(1000); // need to handle exception here
System.gc(); // pull the big GC handle
}
}
}