简体   繁体   中英

How to lock Java threads so that it locks out others until Platform.runLater has finished

In my OSGi based application bundles can be started and stopped at any time. The UI facing bundles register a javafx Node with the 'desktop' bundle. Manipulating the UI must be done on the JavaFX UI thread using Platform.runLater() , which will schedule it for execution at a later time.

I would like to make sure that only one caller can modify the desktop at a time. I used a lock and a condition to achieve this.

public class DesktopContent implements ContentManager {
  private final Pane desktop;
  private final AtomicReference<Node> weather = new AtomicReference<>();
  private final ReentrantLock lock = new ReentrantLock();

  @Override
  public void setWeatherWidget(Node node) {
    updateFX(() -> replaceNode(desktop.getChildren(), weather, node));
  }

  private void updateFX(Runnable runnable) {
    lock.lock();
    Condition condition = lock.newCondition();
    try {
        if (Platform.isFxApplicationThread()) {
            runnable.run();
        } else {
            updateFX(runnable, condition);
            condition.await();
        } 
    } catch (InterruptedException e) {
        logger.log(Level.WARNING, "While waiting for 'updating' condition", e);
    } finally {
        lock.unlock();
    }
  }

  private void updateFX(Runnable runnable, Condition condition) {
    Platform.runLater(() -> {
        lock.lock();
        try {
            runnable.run();
        } finally {
            condition.signal();
            lock.unlock();
        }
    });
}

private void replaceNode(List<Node> children, AtomicReference<Node> current, Node newNode) {
    SequentialTransition st = new SequentialTransition();
    ObservableList<Animation> transformations = st.getChildren();
    Node oldNode = current.getAndSet(newNode);

    if (oldNode != null) {
        FadeTransition ft = new FadeTransition(Duration.millis(350), oldNode);
        ft.setToValue(0d);
        ft.setOnFinished(e -> children.remove(oldNode));
        transformations.add(ft);
    }

    if (newNode != null) {
        newNode.setLayoutX(100d);
        newNode.setLayoutY(100d);
        FadeTransition ft = new FadeTransition(Duration.millis(350), newNode);
        ft.setFromValue(0d);
        ft.setToValue(1d);
        children.add(newNode);
        transformations.add(ft);
    }

    st.play();

}

}

I'm not sure this is right. If the caller is not on the application thread, the framework will schedule the execution of the runnable, and the caller will wait until the condition is met.

But if in the meantime somebody else would call setWeatherWidget I assume it will happily be granted the lock, scheduling another runLater . This means I could just as well skip any locking at all.

How should this be properly handled using locks? Introduce yet another 'global' lock? Or is this over-engineering and all this locking business is unnecessary?

I think you can do this without getting into lower-level details with locks, conditions, etc. Essentially you want to make all calls to setWeatherWidget single-threaded (only one can be called at once), while of course making sure that code that must be performed on the FX Application Thread is done so. The point is that the FX Application Thread is already a single thread: so you can achieve this by sending requests to the FX Application Thread and blocking until those requests are complete:

public class DesktopContent implements ContentManager {
    private final Pane desktop;
    private final AtomicReference<Node> weather = new AtomicReference<>();

    @Override
    public void setWeatherWidget(Node node) {
        updateFX(() -> replaceNode(desktop.getChildren(), weather, node));
    }

    private void updateFX(Runnable runnable) {
        try {
            if (Platform.isFxApplicationThread()) {
                runnable.run();
            } else {
                FutureTask<Void> task = new FutureTask<>(runnable, null);
                Platform.runLater(task);
                // block until task completes:
                task.get();
            } 
        } catch (InterruptedException e) {
            logger.log(Level.WARNING, "While waiting for 'updating' condition", e);
        }
    }

    private void replaceNode(List<Node> children, AtomicReference<Node> current, Node newNode) {
        SequentialTransition st = new SequentialTransition();
        ObservableList<Animation> transformations = st.getChildren();
        Node oldNode = current.getAndSet(newNode);

        if (oldNode != null) {
            FadeTransition ft = new FadeTransition(Duration.millis(350), oldNode);
            ft.setToValue(0d);
            ft.setOnFinished(e -> children.remove(oldNode));
            transformations.add(ft);
        }

        if (newNode != null) {
            newNode.setLayoutX(100d);
            newNode.setLayoutY(100d);
            FadeTransition ft = new FadeTransition(Duration.millis(350), newNode);
            ft.setFromValue(0d);
            ft.setToValue(1d);
            children.add(newNode);
            transformations.add(ft);
        }

        st.play();

    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM