Подтвердить что ты не робот

Есть ли способ закрыть приложение Java, которое использует java3d, не вызывая System.exit()?

Java3D запускает несколько потоков системы и не устанавливает на них флаг isDaemon. Когда я устанавливаю (только) JFrame моего приложения, он не будет завершен, потому что эти потоки все еще запущены.

Вызов System.exit() кажется единственным способом прекратить работу приложения. (Или, конечно, убить его извне).

Как мне не нравится звонить System.exit() Я пробовал следующее (но безуспешно):

  • вызов removeAllLocales() в VirtualUniverse: это завершает большинство потоков, но все же остается один (с именем J3D-Renderer-1).
  • используя отражение для получения ссылки на поле ThreadGroup rootThreadGroupp в javax.media.j3d.MasterControl и установка isDeamon true в этой ThreadGroup. Это не показало никакого эффекта.
  • получить ссылку на ThreadGroup с именем "Java3D" и вызвать interrupt() на нем: это привело к тому, что потоки java3d записывали InterruptedException в stderr, но больше ничего.
  • найдите источники библиотеки Java3d-core и предложите патч: здесь я нашел репозиторий: https://github.com/hharrison/java3d-core и здесь: https://java.net/projects/j3d-core/sources. Позже он выглядит "официальным", но показывает, что последние изменения произошли 5 лет назад, а первый - для меня вилкой.

Я близок к отказу и звоню в System.exit(), но мне все равно не нравится. Вы знаете лучший способ?

4b9b3361

Ответ 1

Одним из возможных решений является вызов метода Java3dThread.finish() в потоках Java3D. Недостатком является то, что для обращения к этому методу нужно обходить правила доступа к java, поскольку он является приватным для пакета. Этот код помогло:

public void dispose() {
    virtualUniverse.removeAllLocales();
    try {
        // give the Java3D threads the chance to terminate peacefully.
        Thread.sleep(250);
    } catch (final InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    // and now handle the threads that didn't take the hint
    finishJava3dThreads();
}

private static void finishJava3dThreads() {
    final Class<?> rendererClass;
    final Method finishMethod;
    try {
        rendererClass = Class.forName("javax.media.j3d.J3dThread");
        finishMethod = rendererClass.getDeclaredMethod("finish");
        finishMethod.setAccessible(true);
    } catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) {
        throw new RuntimeException(e);
    }
    final ThreadGroup[] groups = new ThreadGroup[10];
    final int count = Thread.currentThread().getThreadGroup().getParent().enumerate(groups);
    for (int i = 0; i < count; i++) {
        final ThreadGroup threadGroup = groups[i];
        if ("Java3D".equals(threadGroup.getName())) {
            threadGroup.setDaemon(true);
            final Thread[] threads = new Thread[threadGroup.activeCount()];
            final int threadCount = threadGroup.enumerate(threads);
            for (int j = 0; j < threadCount; j++) {
                final Thread thread = threads[j];
                if (rendererClass.isInstance(thread)) {
                    try {
                        finishMethod.invoke(thread);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
            Thread.yield();
            threadGroup.interrupt();
        }
    }
}

Где virtualUniverse является экземпляром VirtualUniverse, созданным ранее в приложении.

Теперь я вызываю этот метод dispose() для завершения Java3D при завершении приложения:

   addWindowListener(new WindowAdapter() {
        @Override
        public void windowClosed(final WindowEvent e) {
            plater.dispose();
        }
    });

Где plater - это экземпляр, содержащий метод dispose() сверху.

Все остальное просто располагает основной JFrame:

    actions.put(EXIT_ACTION, new AbstractAction(EXIT_ACTION) {
        @Override
        public void actionPerformed(final ActionEvent e) {
            dispose();
        }
    });

а операция закрытия по умолчанию также установлена ​​на DISPOSE_ON_CLOSE:

    setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

Не уверен, что это лучший вариант. (Я по-прежнему предпочитаю его по вызову System.exit(), но использование отражения таким образом несколько хрупко.)

Ответ 2

System.exit(0) - правильный способ завершения программы.

Это удобный способ обработки выключения в программе, где части программы не могут знать друг о друге. Затем, если какая-то часть захочет выйти, он может просто вызвать System.exit(), и крючки выключения позаботятся о выполнении всех необходимых задач остановки, таких как: закрытие файлов, удаление открытых окон и освобождение ресурсов.

Прочитайте документ о выключениях крюка.