0

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

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


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

主题- 界面

public interface Subject {

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

SubjectImpl - 实现主题

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) {
}
}

文件内容系统- 作为观察者中的具体主题(只有一个)

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) ^^
}

}
4

2 回答 2

3

我相信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'supdate()被调用时,它会更改 Editor2's textArea,并且FileContentSubject 也是DocumentListener其中的一个textArea(就像是 Editor1's 一样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()尝试继续迭代到下一步时,它注意到列表已更改并且... bam!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改变自身时,他将不再通知(the ),他只会悄悄地改变。textAreaFileContentSubjectDocumentListener

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

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

于 2013-04-17T05:07:26.000 回答
0

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

确实,当您在迭代时尝试修改数组列表时会收到 ConcurrentModificationException 错误,是吗?

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

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

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

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

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

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

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

于 2013-04-17T04:03:11.863 回答