JavaFX FPS上限为60 FPS

hot*_*zst 2 java multithreading javafx

似乎存在一个共同的理解是,JavaFX的UI线程被以每秒60个更新斗篷(1,2).我的理解是更新意味着pulse.

脉冲是向JavaFX场景图指示是时候将场景图上的元素的状态与Prism同步的事件.脉冲以每秒60帧(fps)的速度进行限制,并在场景图上运行动画时触发.即使动画未运行,当场景图中的某些内容发生变化时,也会调度脉冲.例如,如果改变按钮的位置,则调度脉冲.

资料来源:https://docs.oracle.com/javafx/2/architecture/jfxpub-architecture.htm

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

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)));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的期望是:

  • RunnableS堆叠出来的UI线程中运行,穿插,然后执行所有Runables已排队.
  • UI线程执行前60个调用,然后进一步排队Runable.

然而,在一些初始延迟之后发生了什么事情,其中​​Runables排队,然后由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脉冲上限的概念?
  • 这个小的执行片段不是那么重,所以可能会超出限制吗?

我首先要知道的是Runable,如果线程达到其上限,则在UI线程上推送的内容会发生什么.Runable排队的所有s是在UI线程的一次运行中按顺序执行还是只Runable执行了一次,其余的必须等待?第二种情况会导致严重的问题,当持续推动RunableUI线程上的更多s而不是该限制时.

Jam*_*s_D 5

脉冲上限为60fps.Runnable提交给FX Application Thread的不是.

据我了解,有两个线程:FX应用程序线程和棱镜渲染线程.FX应用程序线程Runnable从队列中消耗s并执行它们.棱镜渲染线程每秒执行多达60次,与FX应用程序线程同步(从而暂时阻止它执行新的runnables)并渲染帧,然后释放FX应用程序线程.

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

所以有几种方法可以让糟糕的事情发生.一种是在FX应用程序线程上有代码(无论是在事件处理程序中还是在Runnable发布到的Platform.runLater()),需要很长时间才能执行.这样做会阻止棱镜渲染线程与FX Application线程同步,这意味着在runnable完成之前无法渲染下一帧.另一个是用太多的Runnables 轰炸FX应用程序线程,这样队列的增长速度就快于消耗它们.(基本上,由于队列总是有可用的东西,FX应用程序线程在这些情况下变成忙线程,并且无法保证棱镜渲染线程能够运行.)

因此,对您的问题的直接回答是,Runnables按照发布的顺序执行,就像它们可以在单个线程上一样.帧渲染(上限为60 fps)将暂时停止Runnable从队列中消耗s.

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

while (fxToolkitRunning()) {
    Runnable runnable = runnableQueue.take(); // block until something available
    acquireLock();
    runnable.run();
    releaseLock();
}
Run Code Online (Sandbox Code Playgroud)

并且棱镜渲染线程看起来像:

while (fxToolkitRunning()) {
    while (! timeForNextFrame()) {
        sleep(timeUntilNextFrame());
    }
    acquireFXApplicationThreadLock();
    if (sceneNeedsUpdating()) {
        renderFrame();
    }
    releaseFXApplicationThreadLock();
}
Run Code Online (Sandbox Code Playgroud)