How to stop one thread from changing the array used by another thread?

I have a Java program that is basically a game. He has a class called "World . " The class "World" has a method of 'levelChanger ()' , and the other method is 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 creates a 2d array of type Color . This array stores Color objects from a PNG image. This array is used by the paint () JPanel method to paint the world on the screen.

The levelChanger () method is used to change the levels (stages) of a game when a particular encoding is true. This is the method that calls the makeColorArray () method to recreate the color array when the game level changes.

The problem is that I have a game loop that runs in a stream. Now the picture of the swing components, such as JPanel, is executed on a different java background thread. When the game level changes, the color array object is re-executed. Now, as I said, the color-array object is used by the paint () method to paint the world on the screen. Sometimes the color grid object is still used by the background stream (unfinished painting), when, according to the logic of the game, the object is the color array is re-created, and its members are set to zero. This throws a null pointer exception , only occasionally. Obviously a race condition.

I want to know how I can stop my game stream from resetting the color array until the background rock has finished drawing.

+6
source share
3 answers

Suggestions:

  • Use the Model-View-Control project for your program, or use one of many such options.
  • Use the Swing timer to control the outline of the graphical user interface, but use real-time slices rather than timer delays in your model to determine the length of time between steps in a loop.
  • The model should run in the Swing GUI event stream.
  • But its lengthy tasks must be done in the background thread using SwingWorker.
  • This is the key. Do not update model data, data used by JPanel for drawing, until the background thread completes work on it. PropertyChangeListener and SwingPropertyChangeSupport can be useful for this.
  • Make sure JPanel uses the paintComponent(...) method and not the paint(...) method, and you call the super method.
  • Best of all, if you make a BufferedImage background image and get JPanel draw, which is in its paintComponent(...) method for efficiency.
  • Make sure that all Swing GUI code, with the possible exception of repaint() calls, is called in the Swing event stream.
  • And yes, definitely read Concurrency in the Swing tutorial .
+1
source

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

Personally, I would like the shared data to be assigned to a separate class that is completely thread safe, so you would not need to make sure that the two separate parts of the code base need to know what to synchronize (it looks like your class here does more than just process the map at first glance, perhaps this is the 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 } } } } } 
+1
source

If you only need one thread changing colorArray once, make it synchronized .

The purpose of synchronization is that the thread requires an object lock. Any other thread that tries to access this object during its blocking will be blocked (it will wait).

See this post: Java: how to synchronize array access and what are the restrictions on what happens in a synchronized state

+1
source

Source: https://habr.com/ru/post/979959/


All Articles