Is there a way to terminate a java application that uses java3d, without calling System.exit()?

前端 未结 2 751
自闭症患者
自闭症患者 2021-02-19 09:49

Java3D starts several system threads and doesn\'t set the isDaemon flag on them. When I dispose the (only) JFrame of my application it won\'t terminate because these threads are

2条回答
  •  时光说笑
    2021-02-19 10:02

    One possible solution is to call the Java3dThread.finish() method on the Java3D threads. The drawback is that one has to bypass java access rules to call this method, as it is package-private. This code did the trick for me:

    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();
            }
        }
    }
    

    Where virtualUniverse is the instance of the VirtualUniverse created earlier in the application.

    Now I call this dispose() method to terminate Java3D when terminating the application:

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

    Where plater is the instance containing the dispose() method from above.

    Everything else just disposes the main JFrame:

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

    and the default close operation is also set to DISPOSE_ON_CLOSE:

        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    

    Not sure if this is the best option, though. (I still prefer it over the System.exit() call, but using reflection in this way is somewhat fragile.)

提交回复
热议问题