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

Java自定义Canvas多实例随机显示对方图像问题排查求助

问题排查与修复方案

根因分析

  • BufferStrategy 初始化时机错误:构造函数执行时组件还未绑定原生窗口对等体,此时调用createBufferStrategy创建的缓冲无效,容易出现多实例缓冲复用,是图像串显的核心原因。
  • 线程安全缺失:每秒刷新的逻辑大概率在非UI线程执行,bImage变量没有可见性保护,图形绘制操作也未限制在Swing事件调度线程(EDT),多线程并发下会出现数据错乱。
  • 渲染逻辑冲突:paint重写主动调用render,与setImage触发的render形成重复调用,同时render内主动调用setSize会触发额外重绘,加重缓冲操作冲突概率。
  • 缺少缓冲区校验:未处理缓冲丢失、内容恢复的场景,容易出现绘制异常。

修复后代码

package com.imagedisplay;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;

import javax.swing.SwingUtilities;

import com.gamedeconstructor.image.ImageEngine;

public class View3d extends Canvas 
{
    // 加volatile保证多线程下可见性
    private volatile BufferedImage bImage;

    public View3d()
    {
        revalidate();
        repaint();
    }

    public void setImage(BufferedImage inImage)
    {
        // 所有UI相关操作强制放到EDT执行,避免并发冲突
        if (SwingUtilities.isEventDispatchThread()) {
            bImage = inImage;
            render();
        } else {
            SwingUtilities.invokeLater(() -> {
                bImage = inImage;
                render();
            });
        }
    }

    // 重写addNotify,组件绑定原生对等体后再初始化缓冲,保证有效性
    @Override
    public void addNotify() {
        super.addNotify();
        // 用双缓冲避免闪烁,单缓冲也可以根据需求调整
        createBufferStrategy(2);
    }

    public void render()
    {
        BufferStrategy bs = this.getBufferStrategy();
        if (bs == null || bImage == null) {
            return;
        }
        // 标准缓冲绘制流程,处理内容丢失、恢复场景
        do {
            do {
                Graphics g = bs.getDrawGraphics();
                try {
                    // 先清屏,避免旧内容残留
                    g.setColor(Color.BLACK);
                    g.fillRect(0, 0, getWidth(), getHeight());
                    // 直接用当前组件宽高,不需要依赖父容器,也不要主动setSize触发重绘
                    BufferedImage scaled = ImageEngine.scale(bImage, getWidth(), getHeight());
                    g.drawImage(scaled, 0, 0, null);
                } finally {
                    g.dispose();
                }
            } while (bs.contentsRestored());
            bs.show();
        } while (bs.contentsLost());
    }
}

额外注意事项

  • 不要在渲染逻辑中主动调用setSize,组件大小由父容器的布局管理器统一控制即可。
  • 若ImageEngine.scale是静态方法,需要保证该方法本身是线程安全的,无共享全局变量。
  • 父容器添加View3d实例时,要保证布局参数能让View3d撑满父容器,避免缩放比例异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 18:24:05