Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stop one thread from modifying an array which is being used by another thread?

I have a java program which is basically a game. It has a class named 'World'. The "World" class has a method 'levelChanger()', and another method '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);
    }   
}

The makeColorArray() method makes a 2d array of type 'Color'. This array stores Color-objects from a PNG image. This array is used by the paint() method of the JPanel to paint the world on the screen.

The levelChanger() method is used to change the levels (stages) of the game when certain codition is true. It is the method which calls the makeColorArray() method to re-make the color-array while changing game-level.

The problem is that I have a game-loop which runs on a Thread. Now, the painting of swing components like JPanel is done on a different background thread by java. When the game-level is being changed, the color-array object is re-made. Now, like I previously said, the color-array object is used by the paint() method to paint the world on the screen. Sometimes, the color-array object is still being used by the background Thread (not finished painting) when, according to game logic, color-array object is re-made and its members set to null. This is causing a null-pointer exception, only sometimes. Clearly a race condition.

I want to know how can i stop my game thread from resetting the color-array until the background swing thread has finished painting.

like image 765
arandomguy Avatar asked Dec 19 '14 21:12

arandomguy


2 Answers

Suggestions:

  • Use a Model-View-Control design for your program, or use one of the many similar variants.
  • Use a Swing Timer to drive your GUI game loop, but use real time slices, not the timer's delay, in your model to determine length of time between loop steps.
  • The model should run in the GUI Swing event thread.
  • But its long-running tasks should be run in background threads using a SwingWorker.
  • This is key: Don't update the model's data, the data used by the JPanel to draw, until the background thread has completed working on it. A PropertyChangeListener and SwingPropertyChangeSupport can be useful for this.
  • Be sure that the JPanel draws in its paintComponent(...) method, not its paint(...) method, and that you call the super method.
  • Best if you make the background image a BufferedImage, and have the JPanel draw that in its paintComponent(...) method for efficiency.
  • Be sure that all Swing GUI code, except perhaps repaint() calls, are called on the Swing event thread.
  • And yes, definitely read the Concurrency in Swing tutorial.
like image 50
Hovercraft Full Of Eels Avatar answered Oct 15 '22 04:10

Hovercraft Full Of Eels


One way with minimal changes is to synchronize access to the color array.

I personally would have the shared data abstracted out to an individual class which is completely thread-safe, that way you wouldn't have to make sure two separate parts of the code base both have to know what to synchronize on (it seems like your class here does more than just handle the map at first glance, perhaps it is such a class that I am describing though).

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

    }
} 
like image 23
NESPowerGlove Avatar answered Oct 15 '22 04:10

NESPowerGlove