What happens to the ThreadPoolExecutor when thread dies in Java

我只是一个虾纸丫 提交于 2020-06-23 02:47:12

问题


I have created a thread which in turn creates a ThreadPoolExecutor and submits some long running tasks to it. At some point, the original thread dies due to unhandled exception/error. What should happen to the executor (it's local to that dead thread, no external references to it)? Should it be GCed or not?

EDIT: this question was formulated incorrectly from the beginning, but I will leave it as Gray provided some good details of how TPE work.


回答1:


Threads are so called GC roots. This means among other things, that a running (or an unstarted) thread can't be collected. It also means that objects that are being referenced from those threads can't be collected, which is why you can do things like new Thread(new MyRunnable()).start(), or have threadpools running without you having any reference to them.

If the thread is a daemon, it can stop automatically if all other non-daemon threads have stopped. You can have threadpools with daemon threads, but the best thing is to make sure that things are cleaned up properly (i.e. an exception shouldn't kill the thread that's supposed to eventually stop and cleanup the threadpool).




回答2:


What should happen to executor (it's local to that dead thread, no external references to it)? Should it be GCed or not?

The answer is more complex than "yes, it will be if no references are to it". It depends on whether or not the threads running in the ThreadPoolExecutor are still running. This in turn depends on what type of TPE was created and whether or not the "long running tasks" that were submitted to it have finished.

For example, if the tasks have not finished then the threads will still be running. Even if they have finish, if you had a TPE with core threads that did not set allowCoreThreadTimeOut(true) then the threads will not stop. The JVM never garbage-collects a running thread since they are considered GC "roots":

... running threads are, by definition, immune to GC. The GC begins its work by scanning "roots", which are deemed always reachable; roots include global variables ("static fields" in Java-talk) and the stacks of all running threads ...

So the next question is if the threads have references back to the ThreadPoolExecutor and I believe they do. The Worker inner class is the Runnable that is stored in thread.target and is being executed by the Thread so it can't be GC'd. Worker is not static so it has implied references to the outer ThreadPoolExecutor instance. The run() method is actually calling the ThreadPoolExecutor.runWorker() method which references all of the task queues managed by the ThreadPoolExecutor. So the running threads hold references back to the Worker and the TPE so the garbage-collector can't collect the TPE.

For example, here's a typical stack frame of a running pool thread which references the 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)

If, however, the thread-pool tasks have all finished and it has 0 core-threads or the core-threads have timed out, then there would be no Worker threads associated with the ThreadPoolExecutor. Then the TPE would be garbage collected because there were no references to it aside from cyclic ones that the GC is smart enough to detect.

Here's a little sample test program which demonstrates it. If there is 1 core thread then the TPE will never be shut down (through finalize()) even after the worker thread exits after noticing that /tmp/x file exists. This is true even though the main thread doesn't have a reference to it. If, however, there are 0 core threads then after the thread times out (here after 1 second after finishing the last task) the TPE will be collected.

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
        }
    }
}


来源:https://stackoverflow.com/questions/54825742/what-happens-to-the-threadpoolexecutor-when-thread-dies-in-java

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!