【问题标题】:Is this simple Swing-based class thread-safe?这个简单的基于 Swing 的类是线程安全的吗?
【发布时间】:2012-02-22 06:06:48
【问题描述】:

我正在用 Java 制作一个概念验证游戏,并决定(为了练习,但主要是为了好玩)用线程安全的双缓冲制作我自己的简单组件。但是,在并发方面(特别是关于 Swing)我不是很有经验,我想知道我的实现是否有任何我遗漏的问题。

实现如下:

import java.awt.Graphics;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;

public class GamePanel extends JPanel
{

    private static final long serialVersionUID = 1L;

    private BufferedImage mBackImage = null;
    private BufferedImage mFrontImage = null;

    public BufferedImage getBackImage ()
    {
        return mBackImage;
    }

    public void swap ()
    {
        BufferedImage new_back;
        //
        synchronized (this)
        {
            new_back = mFrontImage;
            mFrontImage = mBackImage;
        }
        //
        int width = getWidth (), height = getHeight ();
        if (width > 0 && height > 0)
        {
            if (new_back == null || new_back.getWidth () != width
                    || new_back.getHeight () != height)
                new_back = new BufferedImage (width, height,
                        BufferedImage.TYPE_INT_ARGB);
            //
            mBackImage = new_back;
        }
        else
            mBackImage = null;
    }

    @Override
    public void paintComponent (Graphics g)
    {
        synchronized (this)
        {
            if (mFrontImage == null)
                super.paintComponent (g);
            else
                g.drawImage (mFrontImage, 0, 0, null);
        }
    }

}

我假设getBackImage()swap() 只会被单个线程(游戏循环线程)调用。油漆通过 Swing 计时器触发,在 EDT 中也是如此。我相信围绕mFrontImage 的使用的简单同步块应该足以防止不必要的行为,允许游戏循环线程渲染到背面图像并调用交换,而不必担心 Swing 重绘。我错过了什么吗?

【问题讨论】:

    标签: java swing thread-safety


    【解决方案1】:

    在游戏线程中:

    BufferedImage image = gamePanel.getBackPanel();
    gamePanel.swap();
    

    您的程序现在将处于 Event Dispatch Queue 和游戏线程可以访问同一图像的状态。这意味着如果您在 EDT 将图像绘制到屏幕上的同时绘制图像,则可能会在面板上绘制有趣的东西。

    为了单独读取和写入 frontImage 字段,您是线程安全的,因为所有访问都在同步块内完成。但是,可以更好地编写paintComponent 方法以减少在同步块中花费的时间。那就是:

    @Override
    public void paintComponent (Graphics g)
    {
        BufferedImage localFrontImage;
        synchronized (this)
        {
            localFrontImage = mFrontImage;
        }
        if (localFrontImage == null)
            super.paintComponent (g);
        else
            g.drawImage (localFrontImage, 0, 0, null);
    
    }
    

    【讨论】:

    • 应该想到这一点。谢谢你。我想解决这个问题的简单方法是只有一个图像并锁定它的读取和写入,但是它不再是非常并发的......
    • 但是...只要在调用getBackImageswap 之间完成所有绘图(并且仅对返回的图像),那么一切都应该没问题,是吗?也许在午夜尝试并发是个坏主意;)
    【解决方案2】:
    • Swing 是纯单线程的,所有事件都必须在 EDT 上完成,然后我看不出 synchronized (this) 的原因

    • JLabel 中使用Icon 显示Images in the Swing GUI

    • 对于任何swap's types,您可以查看CardLayout

    【讨论】:

    • 没有同步的可能场景?:1) EDT 调用的绘制,mFrontImage != null 2) 当mBackImage == nullmFrontImage 现在为空时,游戏循环线程调用swap 3) EDT 调用@987654331 @与mFrontImage == null
    猜你喜欢
    • 2011-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多