【问题标题】:Java concurrency with arraylists (How to handle this?)Java 并发与数组列表(如何处理?)
【发布时间】:2013-04-09 16:20:16
【问题描述】:

确实,当您在迭代时尝试修改数组列表时会收到 ConcurrentModificationException 错误,是吗?但是,如果您先删除要修改的元素,遍历列表,然后再插入该元素,错误如何仍然存在?我不明白逻辑是否是这样的,为什么它会抛出并发修改异常。

整个程序使用观察者模式。


程序的目的是有多个文本框,当你输入一个时,其他的会更新为相同的文本。

主题 - 界面

public interface Subject {

public void attach(Observer o);
public void detach(Observer o);
public void notifyAllObservers();
}

SubjectImpl - 实现 Subject

import java.util.*;

public class SubjectImpl implements Subject {

private List <Observer> observers;

public SubjectImpl(){
    observers = new ArrayList<Observer>();
}


@Override
public void attach(Observer o) {
    observers.add(o);
}

@Override
public void detach(Observer o) {
    observers.remove(o);
}

@Override
public void notifyAllObservers() {
            //Iterating code
    for(Observer o: observers){
        o.update();
    }
}
}

观察者 - 界面

public interface Observer {
public void update();
}

编辑器 - 实现观察者

import java.awt.*
import java.io.*;
import javax.swing.*;



public class Editor extends JFrame implements DocumentListener, Observer {

private FileContentSubject reference;

    private JScrollPane textAreaScrollPane;
    private JTextArea textArea;


public Editor(FileContentSubject filecontentsubject) throws IOException {
    super("Editor");
    initComponents();

    this.reference = filecontentsubject;
    textArea.getDocument().addDocumentListener(reference);
    textArea.getDocument().putProperty("ownerEditor", this); 
}


private void initComponents(){

    textArea = new JTextArea();       

    //set location, size, visible and defaultcloseoperation here        

    getContentPane().setLayout(new BorderLayout());
    getContentPane().add(textArea, BorderLayout.CENTER);
}



@Override
public void update() {
    textArea.setText(reference.getState()); //Call to update each text box
}


@Override
public void changedUpdate(DocumentEvent e) {
}


@Override
public void insertUpdate(DocumentEvent e) {
}


@Override
public void removeUpdate(DocumentEvent e) {
}
}

文件内容系统 - 在 Observer 中充当具体主题(只有一个)

import javax.swing.event.*;
import javax.swing.text.*;

public class FileContentSubject implements Subject, DocumentListener {

private String state; //Current, most recent copy of everything

public String getState() {
    return this.state;
}

private SubjectImpl reference;

@Override
public void attach(Observer o) {
    reference.attach(o);
}

@Override
public void detach(Observer o) {
    reference.detach(o);
}

@Override
public void notifyAllObservers() {
    reference.notifyAllObservers();
}

public FileContentSubject() {
    reference = new SubjectImpl();
}

@Override
public void changedUpdate(DocumentEvent arg0) {
}

@Override
public void insertUpdate(DocumentEvent arg0) {

    Document doc = (Document) arg0.getDocument();
    try {
        this.state = doc.getText(0, doc.getLength());
    } catch (BadLocationException e) {
        e.printStackTrace();
    }

    Editor e = (Editor) doc.getProperty("ownerEditor");
    reference.detach(e); 
    notifyAllObservers(); 
    reference.attach(e); 
}

@Override
public void removeUpdate(DocumentEvent arg0) {

   //same as insertUpdate(DocumentEvent arg0) ^^
}

}

【问题讨论】:

  • 我也对缺少任何迭代代码感到困惑...发布与异常相关的代码...
  • 确实能尽快得到更好的帮助,请发帖SSCCE

标签: java design-patterns observer-pattern concurrentmodification


【解决方案1】:

我相信您的ConcurrentModificationException 只有在使用 3 个或更多编辑器时才会被抛出,对吧?不管怎样,让我试着解释一下发生了什么。

假设您有 3 个Editor 实例:“Editor1”、“Editor2”和“Editor3”。

当您在“Editor1”中输入文本时会发生这种情况(或者,我如何称呼它,当您“开始事件链”时):

User types on Editor1 // chain begins
FileContentSubject detaches editor: Editor1
FileContentSubject begin notifyAllObservers(): [Editor2, Editor3]
    Editor2 receives update() to new state

到目前为止,正常行为,对吗?问题是当 Editor2 的 update() 被调用时,它会更改 Editor2 的 textArea,而 FileContentSubject DocumentListenertextArea 以及(因为它是 Editor1 的 textArea )。

这会触发一个新版本,从而创建一个“事件链”。在实践中,会发生以下情况(从头开始重复):

User types on Editor1 // chain begins
FileContentSubject detaches editor: Editor1
FileContentSubject begin notifyAllObservers(): [Editor2, Editor3] // first notification
    Editor2 receives update() to new state // another chain begins! (the 1st chain didn't end yet!)
        FileContentSubject detaches editor: Editor2
        FileContentSubject begin notifyAllObservers(): [Editor3]
            Editor3 receives update() to new state // chain THREE!!
                FileContentSubject detaches editor: Editor3
                FileContentSubject begin notifyAllObservers(): [] // empty list... nobody to notify
                FileContentSubject reattaches editor: Editor3
                FileContentSubject ended notifyAllObservers(): [Editor3] // chain 3 over
        FileContentSubject reattaches editor: Editor2
        FileContentSubject ended notifyAllObservers(): [Editor3, Editor2] // chain 2 over

这是抛出ConcurrentModificationException 的地方。但它到底在哪里抛出,在代码上?

其实在SubjectImpl(!):

@Override
public void notifyAllObservers() {
    for (Observer o : observers) {
        o.update();
    }
}

有什么问题?

事情是:

  • 请注意,在// first notification 注释的行中有对notifyAllObservers() 的调用
  • 目前附加的观察者列表是(按此顺序)[Editor2, Editor3]。
  • notifyAllObservers(),当被调用时,然后开始在该列表上迭代
    • 迭代的第一步是调用Editor2.update()
      • Editor2.update() 开启了一个全新的链条...
      • 当该链结束时,附加的观察者列表现在是 [Editor3, Editor2] (注意:顺序已更改!)
    • notifyAllObservers() 尝试继续迭代到下一步时,它注意到列表发生了变化,并且……砰! ConcurrentModificationException 被抛出。

为什么?因为您无法更改已开始迭代的列表。 (使用迭代器或“foreach”。)

解决办法:

可能有很多,但最简单的就是停止“事件链”。为此,只需将 update() 上的 Editor 更改为:

@Override
public void update() {
    textArea.setText(reference.getState()); // Call to update each text box
}

收件人:

@Override
public void update() {
    textArea.getDocument().removeDocumentListener(reference);
    textArea.setText(reference.getState()); // Call to update each text box
    textArea.getDocument().addDocumentListener(reference);
}

这是什么意思?这意味着当Editor 改变自己由于其他任何人 而不是在其textArea 上打字时,他不会再通知FileContentSubjectDocumentListener),他会悄悄地改变。

这将从一开始就阻止任何第二个事件链。

希望我设法解释了这个问题!如果我可以更清楚地说明任何一点,请告诉我!干杯!

【讨论】:

  • 再次非常感谢 acdcjunior,您对问题的解释非常透彻,您帮助我彻底了解了这个概念!
  • 多么奇妙而有见地的答案。
【解决方案2】:

由于没有关于代码的问题,我正在回答带有问号的句子。

您确实会在以下情况下收到 ConcurrentModificationException 错误 您在遍历它时尝试修改数组列表,是吗?

没错。 要引发 ConcurrentModificationException,您需要修改列表调用列表上的方法,然后尝试继续迭代。

但是如果您首先删除元素,错误仍然存​​在怎么办 要修改,遍历列表,然后插入 元素回来了?我不明白逻辑是否是这样的,为什么 引发并发修改异常。

当您使用 List.iterator() 创建迭代器时,您将使用 List 创建一个合约,您将使用刚刚创建的迭代器对象对 List 进行所有修改。

如果你直接删除一个元素 - List.remove() - 你违反了合同。

如果你直接添加一个元素 - List.add() - 你违反了合同。

列表或列表迭代器不关心是否修改元素本身。

希望,这将为您澄清事情。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-01-02
    • 2023-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多