有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

尝试清除JavaFX ObservableMap时出现java ConcurrentException

我正在向JavaFX8应用程序添加一些加速器,当我尝试清除场景中的加速器时,我遇到了ConcurrentModificationException。让我解释一下,每个对话框使用一个场景,我创建了一个伪对话框,它只是一个带有覆盖的窗格。添加窗格时,将删除旧窗格的加速器,并添加新窗格的加速器。对话框关闭后,旧加速器将恢复:

public abstract class DialogController{

    private Map<KeyCombination, Runnable> accelerators;
    .
    .
    .

    protected final void loadMainPane() throws IOException {
        .
        .
        .
        pane.sceneProperty().addListener(new ChangeListener<Scene>() {
            @Override
            public void changed(ObservableValue<? extends Scene> observableValue, Scene oldScene, Scene newScene) {
                if (newScene != null)
                    enableAccelerators();
                else
                    disableAccelerators(oldScene);
            }
        });
    }

    protected void enableAccelerators() {
        accelerators = new HashMap<KeyCombination, Runnable>(pane.getScene().getAccelerators());
        pane.getScene().getAccelerators().clear();
        pane.getScene().getAccelerators().put(new KeyCodeCombination(KeyCode.ENTER, KeyCombination.ALT_DOWN), new Runnable() {
            @Override
            public void run() {
                submit(null);
            }
        });
    }

    protected void disableAccelerators(Scene scene) {
        scene.getAccelerators().putAll(accelerators);
    }
}

当一个子类需要添加新的加速器时,它们只需要重写enableAccelerators()方法,调用super并添加所需的新加速器。 然而,有时,当enableAccelerators尝试场景的clear accelerators时,会抛出以下异常:

java.util.ConcurrentModificationException: null
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
at java.util.HashMap$EntryIterator.next(HashMap.java:1463)
at java.util.HashMap$EntryIterator.next(HashMap.java:1461)
at com.sun.javafx.collections.ObservableMapWrapper$ObservableEntrySet$1.next(ObservableMapWrapper.java:588)
at com.sun.javafx.collections.ObservableMapWrapper$ObservableEntrySet$1.next(ObservableMapWrapper.java:576)
at com.sun.javafx.scene.KeyboardShortcutsHandler.processAccelerators(KeyboardShortcutsHandler.java:340)
at com.sun.javafx.scene.KeyboardShortcutsHandler.dispatchBubblingEvent(KeyboardShortcutsHandler.java:168)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:204)
at javafx.scene.Scene$KeyHandler.process(Scene.java:3949)
at javafx.scene.Scene$KeyHandler.access$2100(Scene.java:3896)
at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2036)
at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2493)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:170)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:123)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:197)
at com.sun.glass.ui.View.handleKeyEvent(View.java:517)
at com.sun.glass.ui.View.notifyKey(View.java:927)
at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
at com.sun.glass.ui.gtk.GtkApplication.access$200(GtkApplication.java:48)
at com.sun.glass.ui.gtk.GtkApplication$6$1.run(GtkApplication.java:149)
at java.lang.Thread.run(Thread.java:745)

我怀疑我的代码正在清除加速器,而JavaFX正在迭代它。我试着把清理部分放在一个同步块中,但没有成功:

protected void enableAccelerators() {
    accelerators = new HashMap<KeyCombination, Runnable>(pane.getScene().getAccelerators());
    synchronized (pane.getScene().getAccelerators()){
        pane.getScene().getAccelerators().clear();
    }
    pane.getScene().getAccelerators().put(new KeyCodeCombination(KeyCode.ENTER, KeyCombination.ALT_DOWN), new Runnable() {
        @Override
        public void run() {
            submit(null);
        }
    });
}

我怎样才能避免这个问题


共 (2) 个答案

  1. # 1 楼答案

    您可能需要在Platform.runLater()调用中输入代码来更新加速器。试试看

            public void changed(ObservableValue<? extends Scene> observableValue, Scene oldScene, Scene newScene) {
                Platform.runLater(() -> {
                    if (newScene != null)
                        enableAccelerators();
                    else
                        disableAccelerators(oldScene);
                });
            }
    

    如果FX应用程序线程在处理事件时调用您的代码,那么可以想象(正如您所建议的)它在遍历加速器集合时会这样做。使用Platform.runLater()将导致在处理FX应用程序线程上当前挂起的任何内容后执行代码

    (如果这看起来有点像黑客行为,那么,被指控有罪……)

  2. # 2 楼答案

    我已经解决了避免移除元素的问题。现在,我没有删除映射的元素,而是将值设为null:

    protected void enableAccelerators() {
        accelerators = new HashMap<KeyCombination, Runnable>(pane.getScene().getAccelerators());
        for (KeyCombination combination : accelerators.keySet())
            accelerators.put(combination, null);
        pane.getScene().getAccelerators().put(new KeyCodeCombination(KeyCode.ENTER, KeyCombination.ALT_DOWN), new Runnable() {
            @Override
            public void run() {
                submit(null);
            }
        });
    }