【问题标题】:How to stop one thread from modifying an array which is being used by another thread?如何阻止一个线程修改另一个线程正在使用的数组?
【发布时间】:2015-02-18 20:34:56
【问题描述】:

我有一个基本上是游戏的 java 程序。它有一个名为 'World' 的类。 “World”类有一个方法'levelChanger()',还有另一个方法'makeColorArray()'

public class World {

    private BufferedImage map, map1, map2, map3;
    private Color[][] colorArray;

    public World(int scrWd, int scrHi) {
        try {
            map1 = ImageIO.read(new File("map1.png"));
            map2 = ImageIO.read(new File("map2.png"));
            map3 = ImageIO.read(new File("map3.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        map = map1;

        makeColorArray();
    }

    private void makeColorArray() {
        colorArray = new Color[mapHi][mapWd]; // resetting the color-array
        for(int i = 0; i < mapHi; i++) {
            for(int j = 0; j < mapWd; j++) {
                colorArray[i][j] = new Color(map.getRGB(j, i));
            }
        }
    }

    //color-array used by paint to paint the world
    public void paint(Graphics2D g2d, float camX, float camY) {
        for(int i = 0; i < mapHi; i++) {
            for(int j = 0; j < mapWd; j++) {
                if(colorArray[i][j].getRed() == 38 && colorArray[i][j].getGreen() == 127 && colorArray[i][j].getBlue() == 0) {
                    //draw Image 1
                }
                else if(colorArray[i][j].getRed() == 255 && colorArray[i][j].getGreen() == 0 && colorArray[i][j].getBlue() == 0) {
                    //draw Image 2
                }
            }
        }
    }

    public void levelChanger(Player player, Enemies enemies) {
        if(player.getRec().intersects(checkPoint[0])) {
            map = map2;
            //calls the color-array maker
            makeColorArray();           
        }
        else if(player.getRec().intersects(checkPoint[1])) {
            map = map3;
            makeColorArray();
        }
    }

    public void update(Player player, Enemies enemies) {
        levelChanger(player, enemies);
    }   
}

makeColorArray() 方法创建一个“颜色”类型的二维数组。此数组存储来自 PNG 图像的颜色对象。 JPanel 的paint() 方法使用此数组在屏幕上绘制世界。

levelChanger() 方法用于在某些代码为真时更改游戏的关卡(阶段)。它是调用makeColorArray()方法在改变游戏关卡时重新制作颜色数组的方法。

问题是我有一个在线程上运行的游戏循环。现在,像JPanel这样的swing组件的绘制是由java在不同的后台线程上完成的。当游戏关卡改变时,颜色数组对象被重新制作。现在,就像我之前所说的,paint() 方法使用颜色数组对象在屏幕上绘制世界。有时,当根据游戏逻辑重新制作颜色数组对象并将其成员设置为空时,背景线程(未完成绘制)仍在使用颜色数组对象。这仅在某些时候会导致 空指针异常。显然是竞争条件。

我想知道如何阻止我的游戏线程重置颜色数组,直到背景摆动线程完成绘画。

【问题讨论】:

  • 最好的解决方案是不使用多线程。将主循环替换为javax.swing.Timer 或使用SwingUtilities.invokeAndWait
  • @Banthar 这是解决问题的简单方法,但并不是所有情况下的最佳解决方案。如果更新代码需要很长时间来处理,那么将其移动到 EDT 会阻止以响应及时的方式处理真实的 UI 事件。

标签: java multithreading swing thread-safety


【解决方案1】:

建议:

  • 为您的程序使用模型-视图-控制设计,或使用许多类似变体之一。
  • 使用 Swing Timer 来驱动您的 GUI 游戏循环,但在您的模型中使用实时切片而不是计时器的延迟来确定循环步骤之间的时间长度。
  • 模型应该在 GUI Swing 事件线程中运行。
  • 但其长时间运行的任务应使用 SwingWorker 在后台线程中运行。
  • 这是关键:不要更新模型的数据,JPanel 用来绘制的数据,直到后台线程完成它的工作。 PropertyChangeListener 和 SwingPropertyChangeSupport 对此很有用。
  • 确保 JPanel 在其 paintComponent(...) 方法中绘制,而不是在其 paint(...) 方法中,并且您调用了超级方法。
  • 最好将背景图像设为 BufferedImage,并让 JPanel 在其 paintComponent(...) 方法中绘制它以提高效率。
  • 确保在 Swing 事件线程上调用所有 Swing GUI 代码,可能除了 repaint() 调用。
  • 是的,一定要阅读Concurrency in Swing tutorial

【讨论】:

    【解决方案2】:

    一种更改最少的方法是同步对颜色数组的访问。

    我个人会将共享数据抽象到一个完全线程安全的单独类中,这样您就不必确保代码库的两个独立部分都必须知道要同步的内容(它乍一看,您的课程似乎不仅仅是处理地图,也许我正在描述的就是这样一个课程。

    private void makeColorArray() {
        Color[][] colorArrayTemp = new Color[mapHi][mapWd]; // resetting the color-array
        for(int i = 0; i < mapHi; i++) {
            for(int j = 0; j < mapWd; j++) {
                colorArrayTemp [i][j] = new Color(map.getRGB(j, i));
            }
        }
        synchronized(colorArray)
        {
             colorArray = colorArrayTemp;
        }
    }
    
    //color-array used by paint to paint the world
    public void paint(Graphics2D g2d, float camX, float camY) {
        synchronized(colorArray)
        {
            for(int i = 0; i < mapHi; i++) {
                for(int j = 0; j < mapWd; j++) {
                    if(colorArray[i][j].getRed() == 38 && colorArray[i][j].getGreen() == 127 && colorArray[i][j].getBlue() == 0) {
                        //draw Image 1
                    }
                    else if(colorArray[i][j].getRed() == 255 && colorArray[i][j].getGreen() == 0 && colorArray[i][j].getBlue() == 0) {
                        //draw Image 2
                    }
                }
            }
    
        }
    } 
    

    【讨论】:

      【解决方案3】:

      如果您只想让一个线程同时修改colorArray,请将其设为synchronized

      同步的目的,是它需要一个线程来获取对象上的锁。任何其他在对象锁定时尝试访问该对象的线程都将被阻止(它将等待)。

      看到这个帖子:Java: how to synchronize array accesses and what are the limitations on what goes in a synchronized condition

      【讨论】:

        猜你喜欢
        • 2017-09-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-29
        相关资源
        最近更新 更多