Java 解决swingworker应用程序中的并发修改异常

Java 解决swingworker应用程序中的并发修改异常,java,user-interface,collections,concurrency,swingworker,Java,User Interface,Collections,Concurrency,Swingworker,我正在编写一个应用程序,其中一些模拟在SwingWorker中运行。在此模拟中,一些集合保留SwingWorker的doInBackground()方法中修改的数据。此数据需要由GUI显示,但显然这会失败,因为一旦GUI开始访问集合,就会引发并发修改异常,因为SwingWorker正在修改相同的集合 如果SwingWorker不必等待GUI完成数据绘制,我将如何共享这些数据?我必须复制数据然后发布吗?这会不会使数据量增加很多(有很多数据)?这个问题还有其他解决方法吗 下面是一些简化的代码: pu

我正在编写一个应用程序,其中一些模拟在SwingWorker中运行。在此模拟中,一些集合保留SwingWorker的doInBackground()方法中修改的数据。此数据需要由GUI显示,但显然这会失败,因为一旦GUI开始访问集合,就会引发并发修改异常,因为SwingWorker正在修改相同的集合

如果SwingWorker不必等待GUI完成数据绘制,我将如何共享这些数据?我必须复制数据然后发布吗?这会不会使数据量增加很多(有很多数据)?这个问题还有其他解决方法吗

下面是一些简化的代码:

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
    }
}
公共类世界{
public Set fooSet=new HashSet();
}
公共类模拟器扩展SwingWorker{
私人世界;
私人世界小组世界小组;
公共模拟器(World World、WorldPanel WorldPanel){
这个世界=世界;
this.worldPanel=worldPanel;
}
@凌驾
受保护的Void doInBackground()引发异常{
while(true){
doSomethingWithFooSet()//从结构上修改集合
发布();
}
}
@凌驾
受保护的作废流程(列表作废列表){
worldPanel.repaint();
}
}
公共类WorldPanel扩展了JPanel{
私人最终世界;
公共世界小组(世界世界){
这个世界=世界;
}
@凌驾
公共组件(图形g){
drawWorld()//从世界中的fooSet读取
}
}

别误会我的意思,我理解为什么这不起作用,我只是想知道我应该在这个设计中做些什么来做我想做的事情:访问我的模拟正在修改的相同数据。process()是否在EDT上运行?如果是这样,是否可以让process()更新WorldPanel用于绘制数据的world对象副本中的集合?

我将在gui中添加一个标志,无论它是否显示/访问数据


最初将其设置为false,当您的工作人员使用切换标志重新加载访问部件时,您无法同时更新和显示世界对象,因此有两种解决方法:按顺序更新和显示数据,或克隆数据,以便更新和显示的数据不同。要实现第一个变体,请不要使用SwingWorker。在第二种变体中,如果简单地克隆整个世界是不可接受的,那么让背景线程不仅计算世界的新状态,还计算修改世界图像的命令<代码>发布命令,然后处理命令以更新图片。

发布一些代码会有所帮助。我假设通过访问GUI中的数据,您的意思是以某种方式在结构上修改数据集(这可以解释ConcurrentModificationException背后的原因)。这真的是你想在那里做的吗?我建议您回顾一下您的设计,并考虑将修改与访问分开。不,GUI只是在元素上迭代。只有SwingWorker线程在结构上修改集合,但是如果GUI试图在SwingWorker在结构上修改集合时访问集合,那么这可以被认为是并发修改,我想……你是对的,代码和您的更新有助于澄清您的要求。我建议使用阻塞队列概念,其中SwingWorker线程充当生产者,GUI充当消费者。@Sujay SwingWorker已经具备了充当生产者和消费者的功能:“publish()”和
process()
。这将使计算和显示按顺序完成。这破坏了使用SwingWorker的想法。谢谢,后者奏效了,尽管我对它做了一些修改,以确保它不会在后台任务的每次迭代中绘制图像。我在gui中创建了一个标志,当绘制完成时,该标志被设置为true,之后后台任务可以更改图像并将标志设置为false。然后,只有当标志设置为false(假设后台任务的一次迭代比绘制gui快一点)时,它才会重新绘制面板并使用更新的图像。它确实给后台任务增加了一点开销,但它确实工作得很好。谢谢不确定是否真的需要draw标志:发布的多个调用已经累积到一个进程调用中,因此它不应该堆叠重画调用:True,但我不能在后台任务和面板(EDT的一部分)中使用相同的数据,所以我必须创建一个数据副本,面板可以用来绘制东西。我不想为后台任务的每一次迭代都创建这个副本,只有当gui更新时,这就是标志的作用。如果我将复制操作放在process()方法中,我将与后台任务同时运行,我相信即使在EDT上,也会再次出现并发修改异常,因为后台任务再次修改数据集,这可能是因为我在这里制造了一个大的设计缺陷,看起来是这样的,但在这一点上,除了使用固定大小的对象(如数组)或使用此标志来确保在不进行并发修改(有点像锁)的情况下更新集合之外,我不知道还有什么解决方案