Java: возможно ли создать объект для сбора мусора, который включает внутренний поток сердцебиения?

Быстрый вопрос: возможно ли создать класс, содержащий внутренний бесконечный поток (пульс) и его объекты автоматически удаляются сборщиком мусора?

Длинный вопрос: я намерен создать класс, включающий внутренний бесконечный фоновый поток (например, сердцебиение). Хотя объекты этого класса не должны нуждаться в явном уничтожении и должны быть удалены сборщиком мусора, когда на них больше нет ссылок (когда в этот момент также должен быть уничтожен пульс), аналогично C#.
Проблема в следующем: Java RE не будет выполнять сборку мусора для этих объектов, потому что внутри они содержат работающий поток. Это приводит к бессмертному объекту.

Примеры:

Пример кода C# (работает как положено):

using System;
using System.Threading;

namespace HeartbeatTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Main execution started.");

            executeMethod();

            // Just wait to see some heartbeats
            Thread.Sleep(5000);

            // This shall garbage-collect myObject
            GC.Collect();

            Console.WriteLine("Main execution finished.");
        }

        private static void executeMethod()
        {
            Console.WriteLine("executeMethod() execution started.");
            MyObjectWithHeartbeat myObject = new MyObjectWithHeartbeat();
            Console.WriteLine("executeMethod() execution finished.");
        }
    }


    class MyObjectWithHeartbeat
    {
        private Thread heartbeatThread;

        public MyObjectWithHeartbeat()
        {
            heartbeatThread = new Thread(() =>
            {
                try
                {
                    while (true)
                    {
                        Console.WriteLine("heartbeat...");
                        Thread.Sleep(1000);
                    }
                }
                catch (ThreadInterruptedException)
                {
                    // finish heartbeat execution
                }
            });
            heartbeatThread.Start();
        }

        ~MyObjectWithHeartbeat()
        {
            Console.WriteLine("MyObjectWithHeartbeat destroy");
            heartbeatThread.Interrupt();
        }
    }

}

Вывод С#:

Main execution started.
executeMethod() execution started.
executeMethod() execution finished.
heartbeat...
heartbeat...
heartbeat...
heartbeat...
heartbeat...
MyObjectWithHeartbeat destroy
Main execution finished.


Пример кода Java (не работает):

package heartbeattest;

public class Main {

    public static void main(String[] args) {
        System.out.println("Main execution started.");

        executeMethod();

        // Just wait to see some heartbeats
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // This should garbage-collect myObject
        System.gc();

        System.out.println("Main execution finished.");
    }

    private static void executeMethod() {
        System.out.println("executeMethod() execution started.");
        MyObjectWithHeartbeat myObject = new MyObjectWithHeartbeat();
        System.out.println("executeMethod() execution finished.");
    }

}

class MyObjectWithHeartbeat {

    private Thread heartbeatThread;

    public MyObjectWithHeartbeat() {
        heartbeatThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (true) {
                        System.out.println("heartbeat...");
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        heartbeatThread.start();
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("MyObjectWithHeartbeat destroy");
        heartbeatThread.interrupt();
        super.finalize();
    }
}

Вывод Java:

Main execution started.
executeMethod() execution started.
executeMethod() execution finished.
heartbeat...
heartbeat...
heartbeat...
heartbeat...
heartbeat...
heartbeat...
Main execution finished.
heartbeat...
heartbeat...
heartbeat...
heartbeat...
heartbeat...
heartbeat...
...
(it keeps executing)


Существует ли архитектурный шаблон для преодоления этого тупика JRE "поток продолжает работать, потому что объект существует - объект не уничтожен, потому что поток работает"?


person Nuno    schedule 02.05.2017    source источник
comment
Дайте потоку сердцебиения слабую ссылку на объект.   -  person Louis Wasserman    schedule 02.05.2017
comment
@LouisWasserman, ваш комментарий на самом деле является ответом. Почему бы не опубликовать это как обычный ответ?   -  person AlexR    schedule 02.05.2017
comment
@LouisWasserman В потоке нет явной ссылки на объект.   -  person Nuno    schedule 02.05.2017
comment
Затем дайте ему слабую ссылку на объект, чтобы он мог проверить, когда объект удаляется сборщиком мусора.   -  person Louis Wasserman    schedule 02.05.2017
comment
@LouisWasserman В настоящее время явной ссылки нет, но объект не подвергается сборке мусора (JVM обнаруживает, что запущен внутренний поток). С точки зрения объекта отсутствие ссылки или наличие слабой точки ссылки на него не будет иметь никакого значения, поэтому поведение будет таким же: JVM не будет собирать его мусором.   -  person Nuno    schedule 05.05.2017
comment
Конечно будет. Поток не будет подвергаться сборке мусора, пока он все еще работает. Если вы остановите выполнение потока после того, как целевой объект будет GC'd, разорвав цикл while, вы получите то, что хотите.   -  person Louis Wasserman    schedule 05.05.2017


Ответы (1)


Это не то, как мы бы сделали это в Java. Обычный подход Java:

  • создавать и возражать
  • создать поток и передать этот объект в поток
  • поток содержит слабую ссылку на объект
  • основной цикл выполняется до тех пор, пока слабая ссылка не равна нулю

вот какой-то псевдокод

class Heartbeat extends Thread {

    WeakReference exists;

    Heartbeat(Object alive) {
        exists = new WeakReference(alive)
    }

    public void run() {
        // if the object has not been GC'ed keep running
        while (exists.get() != null) {
            // do something
        }
        // otherwise terminate the thread
    }
}
String alive = new String("hello!"); // explicit new, believe me!
Thread t = new Heartbeat(alive);
t.start();

p.s.: обратите внимание, что System.gc() НЕ разрешено собирать мусор

ps: мы обычно не пишем финализаторы в мире Java, если мы не пишем библиотеки уровня инфраструктуры :-)

person Simone Avogadro    schedule 21.10.2020