【问题标题】:Java - SwingWorker - How to use an arraylist from publish in the EDTJava - SwingWorker - 如何使用 EDT 中发布的数组列表
【发布时间】:2017-08-01 13:43:04
【问题描述】:

当访问正在另一个线程 (SwingWorker) 中处理的列表时,如何避免 java.util.ConcurrentModificationException

我正在尝试使用的详细信息:

  • 一个包含这个“主要”方法的 GUI 类,我认为应该在 EDT 上运行。

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new myGUIwithButton();
            }
        });
    }
    
  • 这个 GUI 有一个画图方法,它接受 Words 的列表,一个包含字符串和坐标的类,并显示它们:

    public void paint(final List<Word> words){
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                // prepare GUI's background, then :
                for(Word word : words){ // <-- this is line 170 in the error shown below
                    // show each word in the GUI
                    // note that I'm not modifying the words list here
                }
    
  • 在 GUI 中按下按钮时,将执行扩展 SwingWorker&lt;List&lt;Word&gt;,List&lt;Word&gt;&gt; 的类的实例。据我了解,这会创建工作线程。被覆盖的doInBackground 方法创建一个单词列表,然后定期发布:

    public List<Word> doInBackground() {
        List<Word> words = new ArrayList<Word>();
        while (!isCancelled()) {
            // do some work, define aNewWord
            words.add( aNewWord );
            publish(words);
            Thread.pause(someTime);
        }
        return words;
    }
    
  • 然后发布的单词会自动发送到被覆盖的process 方法:

    protected void process(List< List<Word> > wordss) { // Executed on EDT ! <3
        // I'm taking only the first list that was published to avoid trouble
        List<Word> words = wordss.get(0); 
        myGUI.paint(words);
    }
    

有什么问题?

当工作线程“快速”运行(暂停时间少于 50 毫秒)时,我经常在 paint 方法中的第 170 行遇到异常(GUI 文件名为 MotsFleches.java):

Exception in thread "AWT-EventQueue-1" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at MotsFleches$2.run(MotsFleches.java:170)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:756)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:726)
at org.GNOME.Accessibility.AtkWrapper$5.dispatchEvent(AtkWrapper.java:700)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

似乎paint 方法中的words 列表正在被修改,而 EDT 正在处理它。我没有在那里修改它,所以它必须来自另一个线程?
我认为该列表只是另一个线程中words 列表的“快照”,因为它是使用publish 方法发送的。显然不是。 那么我应该如何更改以使用来自 SwingWorker 的已发布列表的此类“快照”以 EDT 方法工作?

感谢您提供的任何建议。

注意事项

  • 出现异常后,程序继续正常运行。我只想让它更干净。
  • 我尝试查看Collections.synchronizedList() 甚至synchronized (words){...},但我的所有尝试都失败了,很可能是因为我不知道在这种情况下“同步”是什么意思以及如何使用它。
  • 请注意,paint 方法中的invokeLater 起初似乎没用,因为我总是从 EDT 调用它,但是如果我不使用它,则在创建 GUI 时第一次调用 paint 不起作用(是不是因为它在 GUI 之前执行,尽管在之后被调用?
  • 我显然缺乏很多概念,所以非常感谢细节。

【问题讨论】:

  • 很难提出具体的建议,因为代码 sn-ps 是零散的,我们必须在脑海中拼凑它。如果您可以提供minimal reproducible example 会更容易。至于问题:您在任何地方都使用相同的列表引用,这就是您得到异常的原因。第一步,要创建快照,只需在doInBackground 中手动创建一个新列表并将其传递给publish。看看有没有帮助。
  • 我知道使用“SSCCE”会更好,但是我觉得没有能力这样做,因为重现问题需要 SwingWorker 有一个“长时间”的任务来完成它更改列表,所以我认为我无法通过暂停来模拟它。无论如何,幸运的是我的问题出在我选择的几行代码中。 :)

标签: java user-interface arraylist swingworker


【解决方案1】:

通过发布在 SwingWorker 中创建的 words 列表并继续处理同一实例,您可以使该实例受到同步。您应该更改 doInBackground 方法,为发布创建一个新的 List,如下所示:

public List<Word> doInBackground() {
  List<Word> words = new ArrayList<Word>();
  while (!isCancelled()) {
      // do some work, define aNewWord
      words.add(aNewWord);
      publish(new ArrayList<>(words)); // don't publish words directly but create new list
      Thread.pause(someTime);
  }
  return words;
}

通过此更改,后台作业和您的绘制方法正在处理不同的对象,您的问题应该得到解决。

【讨论】:

  • 它的工作原理谢谢!我不敢相信我已经如此接近解决它:在询问之前,我尝试在绘画方法中创建一个副本,但它没有用。这很奇怪,因为我刚刚尝试过,它也解决了这个问题。我一定是第一次做错了。
  • 在paint中创建一个新列表并不能解决问题,因为创建新列表还需要迭代到旧列表。它可能会降低问题发生的可能性,但您会不时收到异常。实际上,这就是使并发成为编程中最困难的方面之一的原因。仅仅通过多次执行你的代码你不能说:我确定它有效。您需要了解线程可以采用的不同路径,并始终假设最坏的情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多