如何阻止一个线程修改另一个线程正在使用的数组?

时间:2014-12-19 21:14:16

标签: java multithreading swing thread-safety

我有一个基本上是游戏的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()方法生成一个'Color'类型的二维数组。此数组存储PNG图像中的颜色对象。 JPanel的paint()方法使用此数组在屏幕上绘制世界。

levelChanger()方法用于在某些编码为真时更改游戏的级别(阶段)。这是调用makeColorArray()方法在更改游戏级别时重新制作颜色数组的方法。

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

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

3 个答案:

答案 0 :(得分:1)

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

我个人会将共享数据抽象出一个完全是线程安全的单个类,这样你就不必确保代码库的两个独立部分都必须知道要同步的内容。 (看起来你的班级在这里做的不仅仅是乍一看处理地图,也许它是我所描述的这样一个类。)

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
                }
            }
        }

    }
} 

答案 1 :(得分:1)

如果您只希望一个线程一次修改colorArray,请将其设为synchronized

同步的目的是,它需要一个线程来获取对象的锁定。

时,任何其他尝试在锁定时锁定该对象的线程都将被阻止(

请参阅此帖子:Java: how to synchronize array accesses and what are the limitations on what goes in a synchronized condition

答案 2 :(得分:1)

建议:

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