簡體   English   中英

LinkedList迭代器拋出並發修改異常

[英]LinkedList Iterator throwing Concurrent Modification Exception

有沒有辦法阻止ListIterator拋出ConcurrentModificationException? 這就是我想要做的:

  • 使用具有要經常執行的特定方法的一堆對象創建LinkedList。
  • 有一定數量的線程(比如N),所有這些線程都負責執行LinkedList中對象的所述方法。 例如,如果列表中有k個對象,則線程n將執行列表中第n個對象的方法,然后轉到第n + N個對象,然后轉到第n + 2N個等,直到它循環回到開頭。

這里的問題在於檢索這些對象。 我顯然會使用ListIterator來完成這項工作。 但是,由於將根據文檔拋出的ConcurrentModificationException,我預測這不會走得太遠。 我希望列表可以修改,並且迭代器不關心。 實際上,預計這些對象將創建並銷毀列表中的其他對象。 我想過幾個解決方法:

  • 創建並銷毀新迭代器以檢索給定索引處的對象。 然而,這是O(n),是不合需要的。
  • 改為使用ArrayedList; 然而,這也是不可取的,因為刪除是O(n)並且列表中有時需要擴展(並且可能會收縮?)。
  • 編寫我自己的LinkedList類。 不想。

因此,我的問題。 有沒有辦法阻止ListIterator拋出ConcurrentModificationException?

你似乎關心性能。 您是否真的測量過使用O(n)vs O(1)算法的性能? 根據您正在進行的操作以及執行操作的頻率,只需使用線程安全的CopyOnWriteArrayList即可。 它的迭代器也是線程安全的。

主要的性能拖累是變異操作(設置,添加,刪除......):每次重新創建一個新列表。

但是,對於大多數應用程序而言,性能足夠好。 我個人會嘗試使用它,配置我的應用程序來檢查性能是否足夠好,如果是的話繼續前進。 如果不是,您將需要找到其他方法。

有沒有辦法阻止ListIterator拋出ConcurrentModificationException?

您以這種方式詢問此問題表明缺乏對如何正確使用線程來提高應用程序性能的理解。

使用線程的整個目的是將處理和IO划分為可以並行執行的獨立可運行實體 - 彼此獨立 如果您要將線程分配到同一個LinkedList上的所有工作,那么您很可能會有性能損失或最小增益,因為保持LinkedList每個線程“視圖”同步所需的同步開銷將抵消任何增益由於並行執行。

這個問題應該是“怎么我停止ConcurrentModificationException ”,應該是“我如何使用線程來提高對象的列表的處理”。 這是正確的問題。

要與多個線程並行處理對象集合,您應該使用ExecutorService線程池。 您可以使用以下代碼創建池。 然后, LinkedList每個條目(在此示例中為Job )將由池中的線程並行處理。

// create a thread pool with 10 workers
ExecutorService threadPool = Executors.newFixedThreadPool(10);
// submit each of the objects in the list to the pool
for (Job job : jobLinkedList) {
    threadPool.submit(new MyJobProcessor(job));
}
// once we have submitted all jobs to the thread pool, it should be shutdown
threadPool.shutdown();
// wait for the thread-pool jobs to finish
threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
synchronized (jobLinkedList) {
    // not sure this is necessary but we need to a memory barrier somewhere
}
...
// you wouldn't need this if Job implemented Runnable
public class MyJobProcessor implements Runnable {
    private Job job;
    public MyJobProcessor(Job job) {
        this.job = job;
    }
    public void run() {
        // process the job
    }
}

您可以使用一個Iterator來掃描列表,並使用Executor通過傳遞給一個線程池來對每個對象執行工作。 這很容易。 以這種方式打包工作單元會產生開銷。 您仍然必須小心使用Iterator方法來修改列表,但這可能會簡化問題。

或者你可以一次完成你的工作,然后在下一個列表修改?

你能分成N個名單嗎?

請參閱@assylias的答案 - 他的建議很好。 我想補充一點,如果您決定編寫自己的鏈表類,則需要仔細考慮如何使其成為線程安全的。

如果多個線程試圖同時修改它,請考慮列表可能被破壞的所有方式。 僅鎖定1或2個節點是不夠的 - 例如,請采用以下列表:

A - > B - > C - > D.

想象一下,一個線程試圖刪除B,就像另一個線程正在刪除C.要刪除B,來自A的鏈接需要“跳”到B到C.但是如果C在那個時候不再是列表的一部分呢? 同樣,要刪除C,需要將B中的鏈接更改為跳轉到D,但是如果B已經從列表中刪除了那么該怎么辦? 當節點同時添加到列表的附近部分時,會出現類似的問題。

如果每個節點有1個鎖,並且在執行“刪除”操作(要刪除的節點以及它之前和之后的節點)時鎖定3個節點,我認為它將是線程安全的。 您還需要仔細考慮添加節點時以及遍歷列表時必須鎖定哪些節點。 為了避免死鎖,您需要確保始終以常量順序獲取鎖定,並且在遍歷列表時,您需要使用“手動”鎖定(這會阻止使用普通的Java監視器 - 您需要明確鎖定對象)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM