Java awt EventListener 异步?

标签 java swing awt

我正在开发 Java awt 应用程序。我们目前有一个类,它实现了 runnable 并为应用程序中的对象调用渲染。我们还有键盘和鼠标监听器,可以调用各种对象上的函数。我注意到快速按下多个键时会出现一个奇怪的错误,经过一番调查后,事件监听器似乎是与正在进行渲染的主线程分开异步调用的。谁能确认 Java awt 事件监听器是异步调用的,并提出一个可能的解决方案?

public class Driver extends Canvas implements Runnable
{
    private boolean running = false;
    private Integer frames;
    private Thread thread;

    private Window window;
    private Mouse mouse;
    private Keyboard keyboard;

    /**
     *  Constructor of Driver
     *  Initiates Window, Mouse, Keyboard, handlers
     */
    public Driver()
    {
        window = new Window("Dominion", this);
        mouse = new Mouse(this);
        keyboard = new Keyboard(this);
    }

    /**
     *  updates classes/variables that change per frame
     */
    public void tick() {
        //Framerate independent calls
    }


    /**
     *  Draws the game onto the window
     *  Calls other handler render to draw their parts
     */
    public void render()
    {
        BufferStrategy bs = this.getBufferStrategy();
        if (bs == null)
        {
            this.createBufferStrategy(2);
            return;
        }
        Graphics g = bs.getDrawGraphics();
        //Start Graphics

        g.setColor(Color.WHITE);
        g.fillRect(0, 0, window.getWidth(), window.getHeight());

//Rest of rendering

        //End Graphics
        g.dispose();
        bs.show();
    }

    /**
     *  Starts thread
     */
    public synchronized void start() {
        thread = new Thread(this);
        thread.start();
        running = true;
    }

    /**
     *  Stops thread
     */
    public synchronized void stop() {
        try {
            thread.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *  Important game function that calls the render and tick methods
     */
    public void run() {
        this.requestFocus();
        long lastTime = System.nanoTime();
        double amountOfTicks = 60.0;
        double ns = 1000000000 / amountOfTicks;
        double delta = 0;
        long timer = System.currentTimeMillis();
        frames = 0;
        while (running) {
            long now = System.nanoTime();
            delta += (now - lastTime) / ns;
            lastTime = now;
            while (delta >= 1) {
                tick();
                delta--;
            }
            if (running)
                render();

            frames++;

            if (System.currentTimeMillis() - timer > 1000) {
                timer += 1000;
                System.out.println("FPS: " + frames);
                frames = 0;
            }
        }
        stop();
    }

    /**
     * getter for window
     * @return window
     */
    public Window getWindow()
    {
        return window;
    }

    /**
     *  Starts up the whole Client side of things
     */
    public static void main(String[] args)
    {
        new Driver();
    }
}

和鼠标

public class Mouse implements MouseListener, MouseMotionListener, MouseWheelListener{
    private Driver d;

    /**
     * Creates a Mouse object
     * @param driver
     */
    public Mouse(Driver driver) {
        this.d = driver;
        d.addMouseListener(this);
        d.addMouseMotionListener(this);
        d.addMouseWheelListener(this);
    }

    /**
     * Invoked when the mouse button has been clicked (pressed
     * and released) on a component.
     * @param e
     */
    @Override
    public void mouseClicked(MouseEvent e) {

    }

    //Other methods cut out

}

最佳答案

Swing 是严格单线程的,Swing 所做的一切都发生在它自己的线程(事件调度线程,又名 EDT)上。这不是 java 程序启动的主线程。很少有 Swing 方法可以从 EDT 外部安全地调用(遗憾的是 javadoc 对此不是很明确)。

大多数 Java GUI 框架都有相同的限制(例如 JavaFX 使用非常相似的方法)。

因此,您附加到 Swing 组件的任何监听器实际上都在 EDT 上运行。绘画,就Component的paint()方法而言,也发生在EDT上。

根据应用程序的要求,有许多方法可以满足 Swings 线程要求,同时满足应用程序要求。

最简单的情况是完全由用户输入驱动的应用程序(在这种情况下,您只需创建一个 GUI,一切都会响应用户引起的事件而发生,例如鼠标单击或按键)。由于所有代码都在监听器中运行,因此 Swing 自动从 EDT 调用它,并且不会发生线程问题。缺点是您无法执行长时间运行的方法,因为它们会阻止 EDT,从而导致 GUI 无响应。

为了解决 GUI 无响应问题,长时间运行的工作被安排在 EDT 之外,例如使用 SwingWorker 实用程序类。这种方法非常适合数据库访问、文件 I/O 或响应用户输入而完成的一般计算。

对于不断更新 GUI,例如游戏,上述方法是不够的。对于简单的游戏,一切都可以在 EDT 上运行,例如SwingTimer 在 Swing 从 EDT 调用应用程序代码时获取常规“滴答声”。以这种方式运行所有逻辑可以避免线程问题。

对于需要处理异步任务的成熟游戏,例如网络、资源流等,多线程方法更适合。这使得事情变得更加复杂,因为应用程序需要确保数据对象是从例如GUI 的加载器线程正确同步,并且一次不会被多个线程修改。游戏状态也是如此,在渲染游戏状态时不得对其进行修改。如果游戏主线程和渲染需要异步(例如,如果渲染很慢,实时游戏逻辑就无法等待),主游戏线程可能需要制作快照副本来传递游戏状态进行渲染。

这只是您可以采取的方式的一个非常粗略的轮廓:)

关于Java awt EventListener 异步?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43350911/

相关文章:

java - MigLayout 只增长最后一行

java - 处理自定义菜单栏不显示

java - 无法调整 JPanel 中列表的大小

java - Eclipse 插件 - SWT_AWT 不适用于 OSX

java - 写入 MongoDB 时有哪些 Java 模型对象选项可用?

java - 对 JTextArea 使用撤消和重做

java - 设置JPanel的大小

java - 在 jdk 11 的 intellij idea 中获取 noclassfoundexception : java. sql.SQLException

java - 在 iReport 中有许多独立的、未处理的数据集

java - 按下按钮时显示返回值?