2014-12-03 66 views
1

我正在使用輪廓掃描儀。對於我想要保存角落/邊緣的每個輪廓。 而不是在輪廓中有一個數組我有一個大的陣列,我分享。原因是它必須在動畫上工作,所以這是爲了優化。如何在這種情況下處理ConcurrentModificationException

這個想法是每個輪廓都是大數組的子列表視圖。

我的工作相當長的一個類,使那麼容易,現在我遇到一個問題:

ArrayList<PVector> vecs = new ArrayList<PVector>(); 

for (int i = 0; i < 10; i++) { 
    vecs.add(new PVector()); 
} 

List<PVector> subList = vecs.subList(0, 5); 

for (int i = 0; i < 10; i++) { 
    vecs.add(new PVector()); 
} 

// ConcurrentModificationException 
for (int i = 0; i < subList.size(); i++) { 

} 

首先,它是Java的設計很差,它確實拋出併發修改如果我使用add(Object)?這應該不會影響我已經擁有的任何子列表,因爲它增加了最後的權利? (邏輯講話)。我的意思是,add(Object)永遠不會影響已經令人興奮的subList,只有add(index, Object)可以做到這一點(和其他東西,如刪除,交換和排序)。

二。什麼是解決這個問題的好方法?我可以讓這個大數組真的很大,所以在我已經創建了一個子列表之後,我不太可能需要添加元素,但是我想知道是否還有其他好的方法來處理它。

這是一類我做的,不得不讓我很容易,但讓我的生活困難,現在:)

public class ListDivisor<T> { 

    List<T> list; 

    int subListStartIndex = 0; 
    int currentGetIndex = 0; 

    InstanceHelper instanceHelper; 


    public ListDivisor(List<T> list, InstanceHelper<T> instanceHelper) { 
     this.list = list; 
     this.instanceHelper = instanceHelper; 
    } 

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 


    public void reset() { 
     subListStartIndex = 0; 
     currentGetIndex = 0; 

     if (instanceHelper.doResetInstances()) { 
      for (T obj : list) { 
       instanceHelper.resetInstance(obj); 
      } 
     } 
    } 

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 


    public List<T> getSubList(int size) { 

     int fromIndex = subListStartIndex; // inclusive 
     int toIndex = fromIndex + size; // exclusive 

     for (int i = list.size(); i < toIndex; i++) { 
      list.add((T) instanceHelper.createInstance()); 
     } 

     subListStartIndex = toIndex; 
     currentGetIndex = toIndex; 

     return list.subList(fromIndex, toIndex); 
    } 

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 

    /** 
    * Returns a subList starting where the previous subList ended till 
    * the latest object added till then. 
    * 
    * @return 
    */ 
    public List<T> getSubList() { 
     return getSubList(currentGetIndex-subListStartIndex); 
    } 

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 


    public T getNext() { 

     if (currentGetIndex >= list.size()) { 
      list.add((T)instanceHelper.createInstance()); 
     } 

     return list.get(currentGetIndex++); 

    } 

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 


    public void clear() { 
     list.clear(); 
     reset(); 
    } 


    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 

    public interface InstanceHelper<T> { 
     public T createInstance(); 
     public boolean doResetInstances(); 
     public void resetInstance(T obj); 
    } 

} 

這是如何使用類的一個小例子:

ListDivisor<PVector> vectorsDivisor = new ListDivisor<PVector>(
    new ArrayList<PVector>(), 
    new InstanceHelper<PVector>() { 
      //@Override 
      public PVector createInstance() { 
       return new PVector(); 
      } 

      //@Override 
      public boolean doResetInstances() { 
       return true; 
      } 

      //@Override 
      public void resetInstance(PVector v) { 
       v.set(0,0,0);    
      } 
    }); 

    PVector v = vectorsDivisor.getNext(); 
    v.set(1, 1, 1); 

    v = vectorsDivisor.getNext(); 
    v.set(2, 2, 2); 

    v = vectorsDivisor.getNext(); 
    v.set(3, 3, 3); 

    subList = vectorsDivisor.getSubList(); 


    println("subList size: "+subList.size()); 
+0

你知道,你可以製作一份子列表的副本,以便修改原件不會導致問題。 – 2014-12-03 03:50:43

+0

那麼,使用listDivisor的'getSubList'返回的子列表會拋出一個ConcurrentModificationException異常嗎? – Edward 2014-12-03 03:52:25

+0

@Edward - 是啊 - 請參閱http://stackoverflow.com/questions/27262602/possible-reason-for-concurrentmodificationexception – 2014-12-03 04:22:48

回答

2

根據的Javadoc ArrayList.sublist

「名單由該方法返回的語義將變爲不確定如果支持列表(即,該列表)在結構上以不同於通過返回列表的方式修改。 (結構上修改是指更改此列表的大小,或者以其他方式干擾它以這樣的方式,在正在進行的迭代產生不正確的結果。)」

ArrayList在JVM中執行您正在使用,「未指定」的語義似乎是它拋出一個ConcurrentModificationException


首先,是可憐的Java設計,它拋出併發修改如果我使用添加(對象)?

沒有。這不是「糟糕的設計」。

他們設計這種方式,故意,並有很好的理由。此外,他們記錄,你會得到未指定的行爲,如果你做你在做什麼。實際上,如果它檢測到併發修改,他們已將子列表類實現爲快速失敗。這是一件好事,並且完全符合API規範。

這應該不會影響我已經有的任何子列表,因爲它增加了最後的權利?

問題是,雖然你的例子是(可能)是安全的,但其他人不是。而且不可能區分安全和不安全的情況,而不會增加大量額外的基礎設施,這些基礎設施很可能會使其他用例的效率降低,而且內存密集程度更高。

(因此我的 「是有原因的」 註釋以上。)

二。什麼是解決這個問題的好方法?

如果不理解代碼實際需要做什麼,很難給出建議。但一些通用的替代方案讓人想起:

  • 更改您的算法,以便您不使用List.sublist方法;例如通過你的名義子列表的第一個和最後一個索引作爲參數。

  • 每次更新後臺列表的時間重新創建子表對象。 (假設你對後備列表的更新是非常安全的,那麼這很簡單,而且很簡單,假設你不在使用子列表的迭代器中間。)

  • 更改您的代碼以使用QueueDeque而不是List

  • 實現自己的列表類(基於ArrayList)不扔CME在這個安全的場景。

1

documentation爲ArrayList中的子列表方法指示子列表只應使用,而後臺列表的結構不發生變化:

如果此方法返回的列表語義將變爲不確定備份列表(即該列表)在結構上以除了通過返回列表之外的任何方式進行修改。 (結構上修改是指更改此列表的大小,或者以其他方式干擾它以這樣的方式,在正在進行的迭代產生不正確的結果。)

雖然你說的沒錯,加入到背襯的結束列表不應引起任何的子列表意外的行爲,爪哇正在在這種情況下,悲觀和投擲ConcurrentModificationException如果後臺列表更改後使用的子表以任何方式。這類似於列表迭代器的快速失敗行爲,這也是在the ArrayList docs解釋,它需要認爲這是更好地清潔失敗,而不是猜錯了哪些併發修改是「安全」,並以意外的行爲而告終。

來解決這個問題最簡單的方法,當然,是在創建子列表之前,所有的結構修改的完成了大的ArrayList。是否有任何理由需要通過擴展ArrayList來交織創建子列表?

如果您確實需要在使用它的小節修改大ArrayList中,我建議不使用子列表而直接與指數成ArrayList的工作。每個輪廓任務都會得到它正在處理的ArrayList的子部分的開始和結束索引,並且它將通過在大ArrayList上重複使用get()來遍歷它們。迭代使用顯式get()調用在這裏很重要,因爲迭代器或for-each循環的迭代將在添加到大ArrayList的末尾時遇到相同的ConcurrentModificationException問題。

或者,如果你真的需要的子列表的每個輪廓任務的List兼容視圖,你可以實現自己的子表類,如果其支持列表獲取add()版到不拋出異常,然後寫一個小ArrayList的子類會覆蓋subList()而不是返回此值。 Java的ArrayListSubList的源代碼是available online,所以如果你想確保你的SubList完成Java的所有工作,你可以參考它。

+0

Java文檔不明確關於*結構*更改。雖然它包含一個通過子列表本身清除()父項的例子。 – 2014-12-03 04:31:37

+0

@ S.D。我很困惑 - 我引用的文檔部分沒有明確提及結構性變化?此外,clear()例子正在修改子列表,而OP詢問父進程的併發修改。 – Edward 2014-12-03 04:38:31

+0

如果支持列表(即,這個列表)在結構上以非返回列表以外的方式修改,則文檔中的這條線未定義。「令人困惑。特別是:「通過返回列表以外的任何方式」。所以我認爲我們需要確定子列表上的操作是否只改變子列表的內部引用或父列表。 – 2014-12-03 04:44:33