JavaFX FPS上限为60 FPS

JavaFX FPS上限为60 FPS,java,multithreading,javafx,Java,Multithreading,Javafx,人们似乎普遍认为javafxui线程每秒更新60次(,)。我的理解是,更新意味着pulse 脉冲是一个事件,它向JavaFX场景图指示 是同步场景图上元素状态的时间 用棱镜。脉冲的最大速度为每秒60帧(fps) 并在场景图形上运行动画时激发。即使 当动画未运行时,当出现某个对象时,会计划一个脉冲 场景图形已更改。例如,如果按钮的位置为 更改后,将计划一个脉冲 资料来源: 为了弄清楚如果对Platform.runLater的调用超过60次会发生什么,我编写了这个小程序: import javafx

人们似乎普遍认为javafxui线程每秒更新60次(,)。我的理解是,更新意味着
pulse

脉冲是一个事件,它向JavaFX场景图指示 是同步场景图上元素状态的时间 用棱镜。脉冲的最大速度为每秒60帧(fps) 并在场景图形上运行动画时激发。即使 当动画未运行时,当出现某个对象时,会计划一个脉冲 场景图形已更改。例如,如果按钮的位置为 更改后,将计划一个脉冲

资料来源:

为了弄清楚如果对
Platform.runLater的调用超过60次会发生什么,我编写了这个小程序:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class HighUITraffic extends Application {

    private int counter = 0;
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {


        StackPane root = new StackPane();


        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                long sheduleTime = System.currentTimeMillis();
                System.out.println("Task "+counter+" run at "+sdf.format(new Date(sheduleTime)));
                Platform.runLater(new RunInUI(counter));
                counter++;
            }
        };
        timer.schedule(task, 0, 10); // every 10ms => 100 per second

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    private static class RunInUI implements Runnable {
        private final int counter;

        public RunInUI(int counter) {
            this.counter = counter;
        }

        @Override
        public void run() {
            long executionTime = System.currentTimeMillis();
            System.out.println("Task "+counter+" executed in Application Thread at "+sdf.format(new Date(executionTime)));
        }
    }
}
我的期望是:

  • Runnable
    s被堆叠起来,UI线程穿插运行,然后执行所有排队的
    Runnable
  • UI线程执行前60个调用,然后进一步排队
    Runable
    s up
但是,在一段初始延迟之后,当可运行程序排队时,UI线程一次处理完所有可运行程序时,两个线程按顺序运行:

任务1281在2016-01-25T18:37:00时运行。269任务1281在中执行 应用程序线程在2016-01-25T18:37:00.269运行任务1282在 2016-01-25T18:37:00.274在应用程序线程中执行的任务1282 2016-01-25T18:37:00.274任务1283在2016-01-25T18:37:00.279运行

在一秒钟内,两个线程的调用都超过了60次(我尝试了100次和200次,没有任何区别)

因此,我感到困惑:

  • 我是否误解了60次脉冲上限的概念
  • 这个小的执行片段是否没有那么重,因此可能会超出限制

我首先想知道的是,如果UI线程达到其上限,那么推送到UI线程上的
Runable
s会发生什么情况。是否所有排队的
都可以运行
在UI线程的一次运行中按顺序执行,还是只有一个
可以运行
执行,其余的必须等待?第二种情况会导致严重的问题,当在UI线程上持续推送超过该限制的
可运行的
时。

脉冲上限为60fps<提交到FX应用程序线程的代码>可运行
s不可用

据我所知,有两个线程:FX应用程序线程和prism渲染线程。FX应用程序线程使用队列中的
Runnable
s并执行它们。prism渲染线程每秒最多执行60次,与FX应用程序线程同步(从而暂时阻止它执行新的可运行程序)并渲染帧,然后释放FX应用程序线程

因此,FX应用程序线程将尽快执行您的
Runnable
,并且不限于每1/60秒运行一次;但在帧渲染期间,它不会从队列中获取新的可运行文件。(FX应用程序线程上的用户事件处理方式与您发布的
Runnable
s类似。)

所以有两种方法可以让坏事发生。一种是在FX应用程序线程上(无论是在事件处理程序中还是在
Runnable
发布到
Platform.runLater()
)需要很长时间才能执行的代码。执行此操作将阻止prism渲染线程与FX应用程序线程同步,这意味着在runnable完成之前无法渲染下一帧。另一种方法是用太多的
Runnable
s轰击FX应用程序线程,这样队列的增长速度就超过了它们的消耗速度。(本质上,由于队列总是有可用的东西,因此在这些情况下,FX应用程序线程会变成一个繁忙的线程,并且不能保证prism渲染线程能够运行。)

因此,您的问题的直接答案是,
Runnable
s按照发布的顺序执行,尽可能快地在单个线程上执行。帧渲染(上限为60 fps)将暂时停止使用队列中的
Runnable
s

在伪代码中,我猜测(这只是我的直觉,它是如何工作的,这不是基于源代码)FX应用程序线程看起来像

while (fxToolkitRunning()) {
    Runnable runnable = runnableQueue.take(); // block until something available
    acquireLock();
    runnable.run();
    releaseLock();
}
棱镜渲染线程看起来像:

while (fxToolkitRunning()) {
    while (! timeForNextFrame()) {
        sleep(timeUntilNextFrame());
    }
    acquireFXApplicationThreadLock();
    if (sceneNeedsUpdating()) {
        renderFrame();
    }
    releaseFXApplicationThreadLock();
}