Что происходит с ThreadPoolExecutor, когда поток умирает в Java - PullRequest
0 голосов
/ 22 февраля 2019

Я создал поток, который, в свою очередь, создает ThreadPoolExecutor и отправляет ему несколько длительных задач.В какой-то момент исходный поток умирает из-за необработанного исключения / ошибки.Что должно произойти с исполнителем (это локально для этого мертвого потока, никаких внешних ссылок на него)?Должно ли это быть GCed или нет?

РЕДАКТИРОВАТЬ: этот вопрос был сформулирован неправильно с самого начала, но я оставлю его, поскольку Грей предоставил некоторые подробные сведения о работе TPE.

Ответы [ 2 ]

0 голосов
/ 22 февраля 2019

Что должно произойти с исполнителем (это локально для этого мертвого потока, никаких внешних ссылок на него)?Должен ли он быть GCed или нет?

Ответ более сложный, чем «да, это будет, если на него не будет никаких ссылок».Это зависит от того, работают ли все еще потоки, запущенные в ThreadPoolExecutor.Это, в свою очередь, зависит от того, какой тип TPE был создан и завершены ли отправленные ему «долго выполняющиеся задачи».

Например, если задачи еще не завершены, потоки все равно будутБег.Даже если они закончили, если у вас был TPE с основными потоками, которые не установили allowCoreThreadTimeOut(true), то потоки не остановятся.JVM никогда не собирает мусор работающего потока, так как они считаются "корнями" GC :

... работающие потоки по определению невосприимчивы к GC.GC начинает свою работу с сканирования «корней», которые считаются всегда достижимыми;корни включают в себя глобальные переменные («статические поля» в Java-разговоре) и стеки всех запущенных потоков ...

Итак, следующий вопрос - если у потоков есть ссылки back на 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 основной поток, то TPE никогда не будет остановлен (через finalize()), даже после того, как рабочий поток завершит работу, заметив, что файл /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
        }
    }
}
0 голосов
/ 22 февраля 2019

Нити - это так называемые GC-корни .Это означает, среди прочего, что работающий (или незапущенный) поток не может быть собран.Это также означает, что объекты, на которые ссылаются эти потоки, не могут быть собраны, поэтому вы можете выполнять такие действия, как new Thread(new MyRunnable()).start(), или запускать пулы потоков без ссылки на них.

ЕслиПоток - это демон, он может автоматически останавливаться, если остановились все остальные потоки, не являющиеся демонами.Вы можете иметь пулы потоков с потоками демонов, но лучше всего убедиться, что все очищено должным образом (т. Е. Исключение не должно уничтожать поток, который должен в конечном итоге остановить и очистить пул потоков).

...