当线程在Java中死亡时,ThreadPoolExecutor会发生什么

时间:2019-02-22 11:06:32

标签: java multithreading threadpool executorservice

我创建了一个线程,该线程又创建了一个ThreadPoolExecutor并向其提交一些长时间运行的任务。在某些时候,原始线程由于未处理的异常/错误而死亡。执行程序应该如何处理(它在该死线程本地,没有外部引用)?是否应该GC?

编辑:这个问题从一开始就被错误地提出,但是我会保留它,因为格雷提供了有关TPE工作原理的一些很好的细节。

2 个答案:

答案 0 :(得分:2)

线程被称为GC roots。这意味着除其他外,无法收集正在运行的(或未启动的)线程。这也意味着无法从这些线程引用的对象被收集,这就是为什么您可以执行诸如new Thread(new MyRunnable()).start()之类的事情,或者在没有任何引用的情况下运行线程池的原因。

如果线程是守护程序,则所有其他非守护程序线程都已停止时,它可以自动停止。您可以使用带有守护程序线程的线程池,但是最好的办法是确保正确清理所有内容(即,异常不应杀死本应最终停止并清理线程池的线程)。

答案 1 :(得分:1)

  

执行器应该怎么办(它是死线程的本地对象,没有外部引用)?应该将其GC吗?

答案比“是的,如果没有引用会更复杂”。这取决于ThreadPoolExecutor中运行的线程是否仍在运行。反过来,这取决于创建了什么类型的TPE以及提交给它的“长期运行的任务”是否已经完成。

例如,如果任务尚未完成,则线程将仍在运行。  即使它们已经完成,如果您的TPE的核心线程没有设置allowCoreThreadTimeOut(true),那么这些线程也不会停止。 JVM永远不会垃圾回收正在运行的线程,因为它们被认为是GC "roots"

  

...根据定义,运行中的线程不受GC的影响。 GC通过扫描被认为始终可以到达的“根”来开始工作。根包括全局变量(Java对话中的“静态字段”)和所有正在运行的线程的堆栈...

所以,下一个问题是线程是否对ThreadPoolExecutor引用了 back ,我相信它们确实如此。 Worker内部类是Runnable中存储的thread.target,并由Thread执行,因此无法进行GC处理。 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
        }
    }
}