Search code examples
javauser-interfacecollectionsconcurrencyswingworker

Solving a concurrent modification exception in a swingworker application


I'm writing an application in which some simulation is run in a SwingWorker. In this simulation some sets keep data which are modified in the doInBackground() method of the SwingWorker. This data needs to be shown by the GUI, but obviously this fails because as soon as the GUI starts accessing the sets a concurrent modification exception is thrown because the SwingWorker is modifying the same sets.

How would I go about sharing this data without the SwingWorker having to wait for the GUI to finish drawing the data? Do I have to copy the data and then publish() those? Won't this increase the amount of data by a lot (there is a lot of data)? Are there other ways around this problem?

Here's some simplified code:

public class World {
    public Set<Foo> fooSet = new HashSet<Foo>();
}

public class Simulator extends SwingWorker<Void, Void> {
    private World       world;
    private WorldPanel  worldPanel;

    public Simulator(World world, WorldPanel worldPanel) {
        this.world = world;
        this.worldPanel = worldPanel;
    }

    @Override
    protected Void doInBackground() throws Exception {
        while (true) {
            doSomethingWithFooSet() //structurally modifies the set
            publish();
        }
    }

    @Override
    protected void process(List<Void> voidList) {
        worldPanel.repaint();
    }
}

public class WorldPanel extends JPanel {
    private final World             world;

    public WorldPanel(World world) {
        this.world = world;
    }

    @Override
    public void paintComponent(Graphics g) {
        drawWorld() //reads from fooSet in world
    }
}

Don't get me wrong, I understand why this doesn't work, I'm just wondering what I should change in this design to be able to do what I want to do: access the same data that my simulation is modifying. Does process() run on the EDT? If so, would it be possible to let process() update the sets in a copy of the world object which the WorldPanel uses to draw the data?


Solution

  • You cannot simultaneously update and display the world object, so you have two ways out: do update and displaying sequentially, or clone data so that updated and displayed data are different. To implement the first variant, just do not use SwingWorker. In the second variant, if simple cloning the whole world is unacceptable, let the background thread compute not just the new state of the world, but also commands to modify the world's image. Publish that commands and then process them to update the picture.