2013-05-09 3 views
13

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

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

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

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

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

+1

Я не касался Java3D за многие годы, но, проверяя источник для одной старой программы Java3D, которую я написал, есть вызов 'System.exit (0)': -/Можете ли вы уточнить, почему вы не делаете хотите использовать это? – andrewdotn

+3

Главным образом потому, что это кажется мне нечистым. И если это единственный способ очистки после использования java3d, будет невозможно написать тесты JUnit, которые используют Java3D. Если тесты JUnit запускают начало потоков Java3D, у меня нет хорошего варианта: я, очевидно, не могу вызывать System.exit() из тестов (это сделало бы невозможным получить результат тестов) поэтому я хотел бы иметь выход в этом случае. Спасибо, что посмотрели! – holgero

+0

Вы попробовали метод 'SimpleUniverse # cleanup()'? Это немного больше, чем 'removeAllLocales()' – hoaz

ответ

2

Одним из возможных решений является вызов метода 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(), но использование отражения таким образом несколько хрупко.)

0

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

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

Прочтите документ о shutdown hooks.

+2

Извините, я не думаю, что этот комментарий отвечает на мой вопрос. – holgero

+0

@holgero, если вы не хотите называть system.exit(), это не значит, что это не правильный способ завершения программы. system.exit() определенно правильный путь для завершения программы. – 1ac0

+0

Возможно, вы даже правы с вашим мнением, но это еще не ответ на мой вопрос. Я думаю, что мой вопрос совершенно конкретный: «Есть ли способ ... * без * вызова System.exit()?» и это * не *: «Каков правильный способ прекратить применение?». – holgero