You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Swing渲染闪烁卡顿问题排查与解决

Java Swing方块移动闪烁卡顿问题的排查与解决

问题场景

开发Java GUI程序,实现WASD按键控制屏幕上方块移动,但运行时方块出现闪烁、卡顿,渲染效果不稳定。

核心代码

package me.analyzers.scs.game;

import me.analyzers.scs.utilities.KeyPressHandler;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class MainPanel extends JPanel implements Runnable{
    final int FPS = 240;
    final int tileSize = 16;
    final int scaling = 3;
    final int realTileSize = tileSize * scaling;
    final int widthX = 16;
    final int widthY = 12;
    final int screenWidth = realTileSize * widthX;
    final int screenHeight = realTileSize * widthY;

    Thread mainLoop;
    KeyPressHandler keyPressHandler = new KeyPressHandler();

    public MainPanel() {
        super();
        this.setPreferredSize(new Dimension(screenWidth, screenHeight));
        this.setBackground(Color.WHITE);
        this.addKeyListener(keyPressHandler);
        this.setFocusable(true);
    }

    public void startGameLoop() {
        mainLoop = new Thread(this);
        mainLoop.start();
    }

    public boolean isRunning() {
        return mainLoop != null;
    }

    int posX = 0;
    int posY = 0;

    @Override
    public void run() {
        long lastTime = 0;
        while(isRunning()) {
            long currentTime = System.currentTimeMillis();
            if(currentTime-lastTime > 1000/FPS) {
                lastTime = System.currentTimeMillis();
                tick();
                repaint();
            }
        }
    }

    public void tick() {
        switch (keyPressHandler.getLastKey()) {
            case 'w' -> posY-=1;
            case 's' -> posY+=1;
            case 'a' -> posX-=1;
            case 'd' -> posX+=1;
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(Color.BLACK);
        g2d.fillRect(posX, posY, realTileSize, realTileSize);
        g2d.dispose();
    }
}

排查过程

  1. 初始怀疑并发问题,尝试多种游戏循环调整:
    • 基于时间差条件执行逻辑
    • 使用Thread.sleep()控制帧率
    • 改用Swing Timer类
      以上方法均未解决卡顿闪烁问题。
  2. 发现若在paintComponent内调用repaint()形成循环,渲染可正常进行,但该方法会被每秒调用数百次,不是合理方案。
  3. 在Windows虚拟机中运行同一Jar包,渲染流畅但方块移动速度远慢于本地机器,不符合JVM跨平台一致性预期。
  4. 观察到持续输入(如频繁点击窗口)可暂时消除卡顿,推测是系统为节省CPU资源限制了GUI进程的渲染频率。

解决方案

最终通过调用Toolkit.sync()解决问题,需在repaint()之后添加该方法,修改后的游戏循环代码如下:

@Override
public void run() {
    long lastTime = 0;
    while(isRunning()) {
        long currentTime = System.currentTimeMillis();
        if(currentTime-lastTime > 1000/FPS) {
            lastTime = System.currentTimeMillis();
            tick();
            repaint();
            // 添加同步语句强制刷新缓冲区
            Toolkit.getDefaultToolkit().sync();
        }
    }
}

原理分析

该问题源于窗口系统的图形事件缓冲机制:Swing的渲染请求可能被系统缓冲,未立即同步到屏幕,导致画面更新不及时、闪烁卡顿。Toolkit.sync()会强制将图形缓冲区的内容同步到屏幕,确保渲染操作立即生效,从而解决了卡顿和闪烁问题。

内容的提问来源于stack exchange,提问作者lzg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 02:48:25