1

子がリストに格納されている巨大なツリー構造を反復処理するシングルスレッドアプリケーションがあります。イテレータは常に変更不可能なリストで動作します。

public List<HierarchyNode> getChildren() {  
        return Collections.unmodifiableList(children);  
}  

それでも、ある時点でConcurrentModificationExceptionが発生しますが、これは変更不可能なリストでは不可能だと思いましたか?反復は訪問者を使用して行われます...これがどのように可能であるかについてのアイデアはありますか?

編集:このリストを変更できるのは、リストを保持しているクラスのコンストラクターだけです。

private final List<HierarchyNode> children;

おそらくこれは、ツリーのメモリ使用量がかなり大きい(> 4GB)という事実に関連していますか?

痕跡:

Testcase: testParserSingleFile(General.NetlistBuilder): Caused an ERROR
null
java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819)
    at java.util.ArrayList$Itr.next(ArrayList.java:791)
    at java.util.Collections$UnmodifiableCollection$1.next(Collections.java:1067)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitArchitectureNode(HierarchyNodeVisitorImplementation.java:20)
    at com.bevm.semantics.netlist.NetlistBuilder.visitArchitectureNode(NetlistBuilder.java:40)
    at com.bevm.hierarchy.ArchitectureNode.accept(ArchitectureNode.java:25)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitEntityNode(HierarchyNodeVisitorImplementation.java:33)
    at com.bevm.semantics.netlist.NetlistBuilder.visitEntityNode(NetlistBuilder.java:33)
    at com.bevm.hierarchy.EntityNode.accept(EntityNode.java:33)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitComponentNode(HierarchyNodeVisitorImplementation.java:27)
    at com.bevm.hierarchy.ComponentNode.accept(ComponentNode.java:25)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitInstanceNode(HierarchyNodeVisitorImplementation.java:45)
    at com.bevm.semantics.netlist.NetlistBuilder.visitInstanceNode(NetlistBuilder.java:85)
    at com.bevm.hierarchy.InstanceNode.accept(InstanceNode.java:89)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitArchitectureNode(HierarchyNodeVisitorImplementation.java:21)
    at com.bevm.semantics.netlist.NetlistBuilder.visitArchitectureNode(NetlistBuilder.java:40)
    at com.bevm.hierarchy.ArchitectureNode.accept(ArchitectureNode.java:25)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitEntityNode(HierarchyNodeVisitorImplementation.java:33)
    at com.bevm.semantics.netlist.NetlistBuilder.visitEntityNode(NetlistBuilder.java:33)
    at com.bevm.hierarchy.EntityNode.accept(EntityNode.java:33)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitComponentNode(HierarchyNodeVisitorImplementation.java:27)
    at com.bevm.hierarchy.ComponentNode.accept(ComponentNode.java:25)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitInstanceNode(HierarchyNodeVisitorImplementation.java:45)
    at com.bevm.semantics.netlist.NetlistBuilder.visitInstanceNode(NetlistBuilder.java:85)
    at com.bevm.hierarchy.InstanceNode.accept(InstanceNode.java:89)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitArchitectureNode(HierarchyNodeVisitorImplementation.java:21)
    at com.bevm.semantics.netlist.NetlistBuilder.visitArchitectureNode(NetlistBuilder.java:40)
    at com.bevm.hierarchy.ArchitectureNode.accept(ArchitectureNode.java:25)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitEntityNode(HierarchyNodeVisitorImplementation.java:33)
    at com.bevm.semantics.netlist.NetlistBuilder.visitEntityNode(NetlistBuilder.java:33)
    at com.bevm.hierarchy.EntityNode.accept(EntityNode.java:33)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitComponentNode(HierarchyNodeVisitorImplementation.java:27)
    at com.bevm.hierarchy.ComponentNode.accept(ComponentNode.java:25)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitInstanceNode(HierarchyNodeVisitorImplementation.java:45)
    at com.bevm.semantics.netlist.NetlistBuilder.visitInstanceNode(NetlistBuilder.java:85)
    at com.bevm.hierarchy.InstanceNode.accept(InstanceNode.java:89)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitGenerateNode(HierarchyNodeVisitorImplementation.java:39)
    at com.bevm.semantics.netlist.NetlistBuilder.visitGenerateNode(NetlistBuilder.java:79)
    at com.bevm.hierarchy.GenerateNode.accept(GenerateNode.java:27)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitArchitectureNode(HierarchyNodeVisitorImplementation.java:21)
    at com.bevm.semantics.netlist.NetlistBuilder.visitArchitectureNode(NetlistBuilder.java:40)
    at com.bevm.hierarchy.ArchitectureNode.accept(ArchitectureNode.java:25)
    at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitEntityNode(HierarchyNodeVisitorImplementation.java:33)
    at com.bevm.semantics.netlist.NetlistBuilder.visitEntityNode(NetlistBuilder.java:33)
    at com.bevm.hierarchy.EntityNode.accept(EntityNode.java:33)
    at com.beckhoff.vmagic.hierarchy.HierarchyNodeVisitorImplementation.visit(HierarchyNodeVisitorImplementation.java:15)
    at General.NetlistBuilder.testParserSingleFile(NetlistBuilder.java:125)
4

5 に答える 5

10

したがってCollection.unmodifiableList、本当にスレッドセーフではありません。これは、基になるの変更不可能なビューを作成するためListです。ただしList、ビューの反復中に基になるものが変更された場合は、CMEを取得します。CMEは、個別のスレッドによって引き起こされる必要はないことに注意してください。次のことを行うと、CMEが発生します。

 for (String e : myList){
     myList.remove(5); // throws CME
 }

より良い選択は、渡されたリストの不変のコピーを作成するGuavaのImmutableListです。

コメントによる明確化のために、投稿されたコードを次のように置き換えます。

 public List<HierarchyNode> getChildren() {  
        return ImmutableList.copyOf(children);  
    }

このListメソッドから返されるものは、CMEをスローしないことが保証されています。

アップデート:

それでもコードでハッピンしている理由を理解しようとしている場合は、次のことを考慮してください。

  1. EVERのリストを変更できますか(に渡されるchildren変更可能ファイルを参照しています)?ListunmodifiableList
  2. 呼び出すクラスのいずれかによってgetChildren、変更可能なリストを保持するクラスが、リストを更新する可能性のある方法で呼び出されるようになりますか?
  3. または、クラスのいずれかが、Iterator複数回使用されるのインスタンスを取得しますか?

ImmutableList

于 2012-11-05T13:03:07.600 に答える
2

このリストを変更できる可能性があるのは、リストを保持しているクラスのコンストラクターだけです。

この場合、変更する必要はまったくありません。常に不変にすることをお勧めします。そうすれば、この問題は発生しません。

コンストラクターでは、次のことができます

List<HierarchyNode> children = new ArrayList<>();
// create/modify collection.

this.children = Collections.immutableList(children);

これにより、メソッドが簡素化されます

public List<HierarchyNode> getChildren() {  
    return children;  
}

これらの変更を行うと、コレクションが変更されている場所が見つかると思います。


問題は、Collections.unmodifiableList()このメソッドによって返される参照を使用して変更できないことです。ラップしているコレクションへの変更を妨げることはありません。

List<String> words = new ArrayList<String>();
words.add("hello");
words.add("world");
List<String> unmodifiable = Collections.unmodifiableList(words);
List<String> copy = new ArrayList<String>(words);

System.out.println("Before modification");
System.out.println("words: " + words);
System.out.println("unmodifiable: " + unmodifiable);
System.out.println("copy: " + copy);

words.remove("hello");
words.add("hi");

System.out.println("\nAfter modification");
System.out.println("words: " + words);
System.out.println("unmodifiable: " + unmodifiable);
System.out.println("copy: " + copy);

プリント

Before modification
words: [hello, world]
unmodifiable: [hello, world]
copy: [hello, world]

After modification
words: [world, hi]
unmodifiable: [world, hi]
copy: [hello, world]

新しいライブラリを追加しない場合の解決策は、コレクションを返す前にコレクションをコピーすることです。

于 2012-11-05T13:10:00.763 に答える
1

次のように、イテレーションでリストを変更しようとしている可能性があります。

Iterator iter = list.iterator();
while (iter.hasNext()) {
  if (someCondition)
    list.remove(someObject);
}

代わりに、これはあなたがすべきことです:

for (int i = 0; i < list.size(); i++) {
 if (someCondition)
    list.remove(i--);
 }

ロジックで許可されている場合は、CopyOnWriteArrayListを使用できます。この実装では、リストを変更するたびに、リストの新しいコピーが提供されます。これは、リストに対して多くの反復を行うが変更はほとんど行わない状況や、特定の反復がリストに対する最近の変更を反映する必要がない状況で役立ちます。私はそれをディスパッチャー/サブスクライバーアプリケーションで頻繁に使用しました、そしてそれはパフォーマンス的に素晴らしい働きをします。

于 2012-11-05T13:25:17.730 に答える
0

コレクションを変更してから、そのコレクションで既存のイテレータを使用することはできません。リストをどこかで変更しようとしていますか(ここでは、元のリストも意味します)?

ConcurrentModificationExceptionから:

この例外は、オブジェクトが別のスレッドによって同時に変更されたことを常に示しているわけではないことに注意してください。

于 2012-11-05T13:04:02.093 に答える
0

申し訳ありませんが、ほとんどの人は正しかったです。簡単な答えは、リストは実際には変更できないということではありません...同じクラスのすべてのオブジェクトはgetChildren()メソッドを使用せず、代わりにプライベートメンバーに直接アクセスします。だから私はコードで検索を続ける必要があります...あなたのコメントは助けになります

于 2012-11-05T14:02:06.257 に答える