【问题标题】:Javafx Apps get hang when perform Thread wait and notifyJavafx 应用程序在执行线程等待和通知时挂起
【发布时间】:2016-11-30 14:47:08
【问题描述】:

我正在使用无限循环从我的应用程序中播放一些动画,效果很好。我需要在用户需要时等待我的线程,并在用户需要时再次启动。为此,我通过单击我的根布局来使用等待和通知线程,首先单击使我的线程等待,然后单击使我的线程运行。这也可以按我的意愿工作。

我的问题是当我快速点击时,这意味着当我等待并立即通知时,我的应用程序会挂起。

那么我该如何解决这个问题???

下面是我的代码:

public class AboutC implements Initializable {

    public VBox mainLayout;
    @FXML
    private
    Label nameLvl = new Label();
    @FXML
    private
    Label rollLvl = new Label();
    @FXML
    private
    Label batchLvl = new Label();
    @FXML
    private
    Label depLvl = new Label();
    @FXML
    private
    Label uniLvl = new Label();
    @FXML
    private Circle circle = new Circle();
    private int count = 0;
    private boolean run = true;
    private Thread thread;
    private Task task;
    private FadeTransition fd;
    private RotateTransition rt;
    private Timeline tm;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        ArrayList<AboutDevelopers> list = new ArrayList<>();
        list.add(....)

        fd = new FadeTransition(Duration.seconds(4), mainLayout);
        fd.setFromValue(0.2);
        fd.setToValue(1.0);
        fd.setCycleCount(2);

        rt = new RotateTransition(Duration.seconds(4), circle);
        rt.setByAngle(360);
        rt.setAutoReverse(true);
        rt.setCycleCount(2);

        KeyFrame keyFrame = new KeyFrame(Duration.seconds(4), new KeyValue(circle.radiusProperty(), 0));
        tm = new Timeline(keyFrame);
        tm.setCycleCount(2);
        tm.setAutoReverse(true);

        task = new Task<Void>() {
            @Override
            synchronized public Void call() throws Exception {
                int i = 0;
                while (true) {
                    if (run) {
                        Platform.runLater(() -> {
                            nameLvl.setText(list.get(count).getName());
                            rollLvl.setText("Roll: " + list.get(count).getRoll());
                            batchLvl.setText("Batch: " + list.get(count).getBatch());
                            depLvl.setText("Department: " + list.get(count).getDepartment());
                            uniLvl.setText(list.get(count).getUniversity());
                            circle.setFill(new ImagePattern(new Image(list.get(count).getImagePath())));

                            fd.play();
                            rt.play();
                            tm.play();

                            count++;
                            if (count >= list.size())
                                count = 0;
                        });
                        sleep(10000);
                    } else
                        wait();
                }
            }
        };
        thread = new Thread(task);
        thread.setDaemon(true);
        thread.start();
    }

    void setStage(Stage stage) {
        stage.setOnCloseRequest(event -> {
            thread.interrupt();
        });
    }

    public void playThread(){
        if (run) {
            run = false;
        } else {
            if(!run){
                synchronized (task) {
                    task.notify();
                }
            }
            run = true;
        }
    }
}

【问题讨论】:

  • 我确信有些问题与您的问题更相似,但这是迄今为止我发现的最好的:stackoverflow.com/questions/34510/what-is-a-race-condition
  • 参见this answer(第二个代码块)。重点:在通知之前更改您的标志,更重要的是 - 在 synchronized 块内。
  • 感谢您的时间,检查您的链接并从第一个链接了解问题,作为您的第二个链接答案,我也添加条件 if(!run) 等待并且我的通知已经具有相同的条件但是问题仍然存在。实际上我的应用程序并没有完全挂起,如果我不退出,它会在一段时间后工作。而且我是新程序员,所以我不明白您在同步块中的标志是什么意思。
  • 加载fxml时为什么要初始化应该注入的字段?
  • 我刚开始通过在线资源学习 javafx,几天前,当我没有初始化这些字段时,我得到了空指针,这就是我初始化这些值的原因。也许这不是一个好的做法,我现在向你学习。

标签: java multithreading javafx


【解决方案1】:
  1. run 不是 volatile 并且被写入同步块之外。这意味着任务可能永远不会看到更新后的值。
  2. 使用Thread.sleep(10000) 不会释放Task 上的锁定,这意味着可能会发生以下情况:
    1. 任务开始休眠
    2. playThread 方法将 run 更改为 false
    3. 再次调用playThread 方法并尝试获取任务对象上的锁,该任务仍保持其自身,导致调用线程被阻塞长达 10 秒

要解决这些问题,请仅从同步块修改 run 字段,并使用带有超时的 wait 而不是 sleep

while (true) {
    if (run) {
        Platform.runLater(() -> {
            nameLvl.setText(list.get(count).getName());
            rollLvl.setText("Roll: " + list.get(count).getRoll());
            batchLvl.setText("Batch: " + list.get(count).getBatch());
            depLvl.setText("Department: " + list.get(count).getDepartment());
            uniLvl.setText(list.get(count).getUniversity());
            circle.setFill(new ImagePattern(new Image(list.get(count).getImagePath())));

            fd.play();
            rt.play();
            tm.play();

            count++;
            if (count >= list.size())
                count = 0;
        });
        wait(10000);
    } else
        wait();
}
public void playThread(){
    synchronized (task) {
        run = !run;
        if (run) {
            task.notify();
        }
    }
}

这意味着启动和停止任务可能会加快更新频率...


替代方案:

使用ScheduledExecutorService 定期安排更新:

// TODO: shut this down after you're done with it???
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(r -> {
    Thread t = new Thread(r);
    t.setDaemon(true);
    return t;
});

@Override
public void initialize(URL location, ResourceBundle resources) {
    ...
    startTask();
}

private final Runnable updateRunnable = () -> {
    Platform.runLater(() -> {
        nameLvl.setText(list.get(count).getName());
        rollLvl.setText("Roll: " + list.get(count).getRoll());
        batchLvl.setText("Batch: " + list.get(count).getBatch());
        depLvl.setText("Department: " + list.get(count).getDepartment());
        uniLvl.setText(list.get(count).getUniversity());
        circle.setFill(new ImagePattern(new Image(list.get(count).getImagePath())));

        fd.play();
        rt.play();
        tm.play();

        count++;
        if (count >= list.size())
            count = 0;
        }
    });
};

private ScheduledFuture scheduledFuture;

private void startTask() {
    scheduledFuture = executor.scheduleWithFixedDelay(updateRunnable, 0, 10000, TimeUnit.MILLISECONDS);
}

public void playThread() {
    if (scheduledFuture == null) {
        // nothing running currently
        startTask();
    } else {
        scheduledFuture.cancel();
        scheduledFuture = null;
    }
}

或者以更适合 JavaFX 的方式

Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(10), evt -> {

        nameLvl.setText(list.get(count).getName());
        rollLvl.setText("Roll: " + list.get(count).getRoll());
        batchLvl.setText("Batch: " + list.get(count).getBatch());
        depLvl.setText("Department: " + list.get(count).getDepartment());
        uniLvl.setText(list.get(count).getUniversity());
        circle.setFill(new ImagePattern(new Image(list.get(count).getImagePath())));

        fd.play();
        rt.play();
        tm.play();

        count++;
        if (count >= list.size())
            count = 0;
        }
    });

}));
timeline.setCycleCount(Animation.INDEFINITE);

timeline.play();
if (timeline.getStatus == Animation.Status.RUNNING) {
    timeline.stop();
} else {
    timeline.play();
}

【讨论】:

  • 我测试了你的第一个解决方案,但唯一的问题是我的信息在没有完整动画周期的情况下更新,我知道这是为了等待使用(10000)。所以当我快速点击时,等待得到通知,我的循环开始了,所以我的信息也改变了。我正在测试您的下两个解决方案,以便我能够将其标记为已接受
  • 我接受了它,但调度程序也给了我与前一个相同的结果,它在播放动画时更新我的​​信息而无需等待 10 秒。最后一个工作很好,我想我会做一些工作来学习。这里的问题是我必须等待 10 秒才能第一次启动时间线动画,而第二次是当我暂停时间线动画时,时间线动画在其运行时暂停,当再次播放时它从那个时间开始播放,所以我必须等待完成它的时间周期。这不是什么大问题,因为有一些设置播放时间的选项,但问题是前 10 秒等待。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-13
  • 1970-01-01
  • 2019-05-08
  • 1970-01-01
  • 2022-01-22
相关资源
最近更新 更多