Java中时间轴(Timeline)和线程(Thread)有什么区别?

5
下面这两个简单的代码片段执行相同的任务:每秒钟打印一次“Hello, world”。但是它们之间有何区别?什么情况下应该使用线程,而什么情况下应该使用时间轴(Timeline)呢?时间轴内部是否启动了一个线程?如果没有,那么如何在不阻塞主线程的情况下每秒执行一次打印操作呢?
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), e -> System.out.println("Hello, world")));
timeline.setCycleCount(-1);
timeline.play();

new Thread(() -> {
    while (true) {
        System.out.println("Hello, world!");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();

在第二个代码片段中,如果我执行 Thread.sleep(some_volatile_variable_of_main_thread_that_changes_overtime),我如何使用 Timeline 实现相同的功能。


你从哪里得到了 Timeline?它是 javafx 吗?如果是的话,请考虑将该标签添加到我们的问题中,因为这个类不是标准的 java 的一部分(至少从 Java 11 开始)。 - Pshemo
@Pshemo 是的,它是JavaFX。我已经添加了标签。这甚至引出了一个新问题,为什么JavaFX中存在Timeline,而它与UI毫无关系。 - Youssef13
1
主要的区别在于Timeline关键帧中的代码保证在FX应用程序线程上执行。时间轴也不会启动新线程,它们只是在现有的FX应用程序线程“脉冲”期间更新。 - James_D
如果你正在创建一个JavaFX程序,使用Timeline。如果是纯Java,则使用Thread。如果需要在JavaFX程序中使用线程思想,请查看Service和/或Task - SedJ601
好的资源:https://docs.oracle.com/javase/8/javafx/interoperability-tutorial/concurrency.htm 和 https://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm - SedJ601
显示剩余8条评论
2个回答

4

Thread 类是Java API的一部分,代表一个后台线程。当一个 Thread 被启动时,它的 Runnable 或者自身的 run 方法中的代码会与其他运行线程并行执行。这使得可能需要较长时间执行的代码能够在不延迟其他可同时运行的代码的情况下执行。但是使用它的代价是,如果数据在不同的线程之间共享,必须非常小心,以确保在任何单个线程中都可以在一致的状态下读取它,并且数据是“活动的”:也就是说,在一个线程中对数据进行的更改实际上可以在其他线程中观察到。

相比之下,Timeline 是JavaFX框架的一部分,特别是它的动画API的一部分。当一个JavaFX应用程序启动时,一个称为 FX Application Thread 的线程开始运行。此线程在循环中运行,并负责呈现UI并处理用户事件(以及其他事情)。UI呈现发生在“脉冲”中,目前的版本每秒目标为60次。由于上述所提到的数据同步问题,所有对UI的更改必须在FX Application Thread上进行。此外,FX Application线程上的代码不能运行时间过长(因此不能使用sleep()暂停或通过IO读取大型数据集),因为这将阻止线程呈现UI。

Timeline 的工作原理是拥有一组 KeyFrame,每个关键帧指定一个时间(以自时间线开始以来测量的 Duration 形式)和一个事件处理程序和/或 KeyValue。在FX Application Thread的每次脉冲中,如果正在运行 Timeline,则FX Application Thread循环将检查是否到了触发任何事件处理程序的时候。对于KeyValue,如果值是可插值的(例如是数字,或实现了Interpolatable),则它的值将通过计算经过的时间与下一个 KeyFrame 的时间的比例来计算。

Timeline 对于简单动画(例如通过使用指定布局位置或平移坐标的 KeyValue 来移动节点等)以及在特定时间执行离散的UI更新非常有用(例如在“记忆”游戏中显示和隐藏图像)。

因此:

  • 附加到时间轴(Timeline)的KeyFrame事件处理程序中的代码可以更新UI,因为保证在FX应用线程上执行。
  • Timeline中作为KeyFrame的一部分更新的KeyValue可能是显示在屏幕上的UI元素的属性。
  • 对于KeyFrame的事件处理程序中的代码不得阻塞执行或执行长时间运行的任务。
  • 尝试在非JavaFX应用程序(即未启动JavaFX运行时的应用程序)中使用Timeline将失败,因为没有JavaFX应用程序线程来执行更新。

相反:

  • 在后台Thread上运行的代码不能更新UI元素(或其属性)。这适用于JavaFX和Java Swing / AWT。
  • 在后台线程上运行的代码可能会阻塞或执行长时间运行的任务。

请注意,标准Java API的java.util.Timer及其相关的TimerTask在内部创建的后台线程上执行。这意味着,尽管TimerTimerTask的API看起来与Timeline类似,但它们必须遵守后台线程的规则(不得更新UI等)。


3
为了理解这两者的区别,需要思考两个主要问题:
- 当我们使用单独的Thread时,能否从同一线程更新JavaFX UI控件? - 给定一个WritableValue,如何使一个Thread在预定的速率、起始值和结束值下随时间改变该值?
根据Timeline文档:
一个时间轴可以用来定义任何可写值的自由形式动画,例如所有JavaFX属性。由一个或多个关键帧定义的时间轴依次处理每个关键帧,按KeyFrame.time指定的顺序进行处理。关键帧的动画属性由KeyFrame.values中的关键值定义,在KeyFrame的指定时间内与目标关键值相互插值,以Timeline的初始位置为基础,取决于Timeline的方向。
例子:弹跳14次的球(每次弹跳需要3秒)。
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(3),
    new KeyValue(ball.layoutXProperty(), parentPane.getBoundsInLocal().getMaxX() - ball.getRadius())));
timeline.setCycleCount(14);
timeline.play();

总的来说,Timeline 的设计是为了通过在 JavaFX 线程中顺序处理一个或多个 KeyFrame 来支持 Animation
  • 何时应该使用线程,何时应该使用 Timeline?

Timeline 应用于动画和以一定速率更新可写 UI 值。而另一方面,Thread 应该用于运行长时间的后台任务(ExecutorService 总是更好的选择)。

  • Timeline 是否在内部启动了新线程?

不,Timeline 不会启动新线程,它使用 JavaFX 线程。

  • 最后一部分并不完全描述 Timeline 的设计目的。如果需要改变时长,需要重新初始化你的 Timeline 或者移除旧的 KeyFrame 并添加一个新的带有新时长的 KeyFrame

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接