简体   繁体   中英

Repeatedly start and stop several threads

I have a grid (a matrix). Each cell is an object. At the beginning, there are five (random) of these cells chosen as "jump cells" in which the user can jump in order to escape from an enemy. Each cell has a random countdown. If the countdown becomes 0 the cell becomes a normal cell and the player can not jump in it anymore. If the user press Enter, the player jumps in one of the current "jump cells" and at the same time a new jump cell is chosen at random.

Sometimes, while I'm playing this game and i press Enter, I get a ConcurrentModificationException , I don't know why.

There is the possibility to press Esc in order to reset the grid situation (create new 5 jump cells). If I press repeatedly the Esc button, very often happens that some new jump cell doesn't decrease its countdown.

Here is the code:

Cell

public class Cell implements Runnable {

/** indicates whether this cell is the target cell */
private boolean isTarget;

/** indicates whether this cell is a jump cell */
private boolean isJumpCell;

/** associated number to this cell */
private int number;

/** living time of this cell */
private int countdown;

/** coordinates of this cell in the grid */
private int i;
private int j;

private boolean running;
private boolean alreadyStarted;

private Thread countdownT;

public Cell(int i, int j) {
    this.i = i;
    this.j = j;
    countdown = (int)(Math.random() * 6) + 3;
    countdownT = new Thread(this);
}

/**
 * Starts the thread or restart if already started in the past
 */
public void start() {
    if(alreadyStarted) {
        restart();
    } else {
        countdownT.start();
        running = true;
        alreadyStarted = true;
        isJumpCell = true;
    }
}

/**
 * This cell is restarted
 */
public void restart() {
    isJumpCell = true;
    running = true;
    countdown = (int)(Math.random() * 6) + 3;
}

/**
 * Stops the update of this cell (is not a jump cell anymore)
 */
public void stop() {
    isJumpCell = false;
    running = false;
}

@Override
public void run() {
    while (running) {
        try {
            Thread.sleep(1000);
            countdown--;
            // if the countdown becomes 0, stop running
            // not a jump cell anymore
            if (countdown == 0) {
                running = false;
                isJumpCell = false;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Grid

I put the essential code

public Grid(RedEnemy p) {
    this.p = p;
    grid = new Cell[ROWS][COLS];
    for(int i = 0; i < ROWS; i++) {
        for(int j = 0; j < COLS; j++) {
            grid[i][j] = new Cell(i,j);
        }
    }
    jumpCells = new ArrayList<Cell>();

    // initial setup
    numJumpCells = 5;

    grid[target_y][target_x].setTarget(true);

    for(int n = 0; n < numJumpCells; n++) {
        int i = (int) (Math.random() * (ROWS - 1)); 
        int j = (int) (Math.random() * (COLS - 1)); 
        grid[i][j].setTarget(false);
        grid[i][j].setNumber(n + 1);
        jumpCells.add(grid[i][j]);
        jumpCells.get(n).start();
    }
}

public void reset() {
    for(Cell cell : jumpCells) {
        cell.stop();
    }
    jumpCells.clear();
    target_x = 0;
    target_y = 0;
    numJumpCells = 5;
    for(int n = 0; n < numJumpCells; n++) {
        int i = (int) (Math.random() * (ROWS - 1)); 
        int j = (int) (Math.random() * (COLS - 1)); 
        grid[i][j].setTarget(false);
        grid[i][j].setNumber(n + 1);
        jumpCells.add(grid[i][j]);
        jumpCells.get(n).start();
    }
}

/**
 * Performs the jump
 */
public void jump() {
    // always jumps in the first jump cell among the current ones
    Cell jumpCell = jumpCells.get(0); 
    // updates the position of the player
    target_y = jumpCell.getI();
    target_x = jumpCell.getJ();
    // updates the cell in the grid
    grid[jumpCell.getI()][jumpCell.getJ()].setJumpCell(false);
    grid[jumpCell.getI()][jumpCell.getJ()].setTarget(true);
    // stop the cell in which the player is jumped
    jumpCells.get(0).stop();
    // removes the cell from the list of the jump cells
    jumpCells.remove(0);

    // updates the position of the player in the enemy class
    p.setTargetY(target_y * SIZE);
    p.setTargetX(target_x * SIZE);

    // create a new jump cell at random
    int i = (int) (Math.random() * (ROWS - 1)); 
    int j = (int) (Math.random() * (COLS - 1)); 
    //grid[i][j].setJumpCell(true);
    grid[i][j].setTarget(false);
    grid[i][j].setNumber(jumpCells.size() - 1);
    jumpCells.add(grid[i][j]);
    jumpCells.get(jumpCells.size() - 1).start();
}

// UPDATE
public void update(float delta) {
    for(Iterator<Cell> it = jumpCells.iterator(); it.hasNext();) {
        Cell c = it.next();
        if(!c.isJumpCell()) {
            it.remove();
            numJumpCells--;
        }
    }
}

// DRAW
public void draw(Graphics dbg) {
    // draw the grid
    dbg.setColor(Color.BLACK);
    int heightOfRow = GamePanel.PHEIGHT / ROWS;
    for (int i = 0; i < ROWS; i++)
        dbg.drawLine(0, i * heightOfRow, GamePanel.PWIDTH, i * heightOfRow);

    int widthdOfRow = GamePanel.PWIDTH / COLS;
    for (int i = 0; i < COLS; i++)
        dbg.drawLine(i * widthdOfRow, 0, i * widthdOfRow, GamePanel.PHEIGHT);

    // draw jump cells
    dbg.setColor(Color.RED);
    dbg.setFont(new Font("TimesRoman", Font.PLAIN, 25)); 
    for(Iterator<Cell> it = jumpCells.iterator(); it.hasNext();) {
        Cell c = it.next();
        dbg.drawRect(c.getJ() * SIZE, c.getI() * SIZE, SIZE, SIZE);
        dbg.setColor(Color.BLUE);
        dbg.drawString(String.valueOf(c.getCountdown()), c.getJ() * SIZE + SIZE/2, c.getI() * SIZE + SIZE/2);
        dbg.setColor(Color.RED);
    }

    // draw target
    dbg.setColor(Color.BLUE);
    dbg.fillRect(target_x * SIZE, target_y * SIZE, SIZE, SIZE);
}

EDIT

TimerThread

public class TimerThread implements Runnable {

private Grid grid;

private boolean isRunning;

public TimerThread(Grid grid) {
    this.grid = grid;
}

public void start() {
    isRunning = true;
}

public void stop() {
    isRunning = false;
}

@Override
public void run() {
    while(isRunning) {
        // retrieves the list of the jump cells
        ArrayList<Cell> jumpCells = (ArrayList<Cell>) grid.getJumpCells();

        // wait one second
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // updates the jump cells
        for(Cell c : jumpCells) {
            if(c.getCountdown() > 0) {
                c.decreaseCountdown();
            } else {
                c.setJumpCell(false);
            }
        }
    }
}

Or...

@Override
public void run() {
    while(isRunning) {
        // retrieves all the cells
        Cell[][] cells = grid.getGrid();

        // wait one second
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // updates the cells
        for(int i = 0; i < cells.length; i++) {
            for(int j = 0; j < cells[0].length; j++) {
                if(cells[i][j].isJumpCell()) {
                    if(cells[i][j].getCountdown() > 0) {
                        cells[i][j].decreaseCountdown();
                    } else {
                        cells[i][j].setJumpCell(false);
                    }
                }
            }
        }
    }
}

Ok with this last update seems it work fine! :)

It's just a guess, because you don't have a stacktrace for us to follow, but I suspect that what's happening when you get the ConcurrentModificationException is that you're executing an update (iterating over jumpCells ) at the same time as you're trying to compute the effect of a jump (and removing one of those jumpCells ). That's not allowed.

You could use a concurrent collection to allow something like this, but that would just force blocking, and it's not very clean for what you're trying to do.

You should only change your jump cells during the update. You can do this by having the jump method set some variable which gets checked during the update.

The best thing to do would be to not multithread the cell timers. There's no reason to use so many threads in this application.

Instead, use a single timer thread that iterates through all the cells every second (or even every 100 millis) and updates them. Add synchronization locks to the cells to make sure they aren't being modified by the game loop and the timer at the same time.

A full stack trace would help diagnose the exact problem you're having. And, if you must multithread, learn to use the debugger really well.

The cause of your exception is that you have multiple threads potentially updating the jumpCells list while the draw method is iterating the list. The same applies for the update method ... which is also updating the list (via it.remove ). If the updates and iterations overlap in time, you will get a ConcurrentModificationException .

(There is a "fail fast" check in the standard non-concurrent list classes that is designed to detect cases where you iterate and update a list simultaneously. It is designed to eliminate problems that are more insidious and hard to find; eg corruption of list data structures.)

There are also potential concurrency problems with your operations on the cell ... unless the getters and setters are properly synchronized.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM