2017-05-18 148 views
2

我有一個ArrayList有兩個訪問器方法和一個通知器。我認爲:ArrayList迭代器拋出ConcurrentModificationException

private final List<WeakReference<LockListener>> listeners = new ArrayList<>(); 

所有訂閱操作使用:

public void subscribe(@NonNull LockListener listener) { 
    for (Iterator<WeakReference<LockListener>> it = listeners.iterator(); it.hasNext();) { 
     // has this one already subscribed? 
     if (listener.equals(it.next().get())) { 
      return; 
     } 
    } 
    listeners.add(new WeakReference<>(listener)); 
} 

所有退訂操作使用:

public void unsubscribe(@NonNull LockListener listener) { 
    if (listeners.isEmpty()) { 
     return; 
    } 

    for (Iterator<WeakReference<LockListener>> it = listeners.iterator(); it.hasNext();) { 
     WeakReference<LockListener> ref = it.next(); 
     if (ref == null || ref.get() == null || listener.equals(ref.get())) { 
      it.remove(); 
     } 
    } 
} 

,並在通告:

private void notifyListeners() { 
    if (listeners.isEmpty()) { 
     return; 
    } 

    Iterator<WeakReference<LockListener>> it = listeners.iterator(); 
    while (it.hasNext()) { 
     WeakReference<LockListener> ref = it.next(); 
     if (ref == null || ref.get() == null) { 
      it.remove(); 
     } else { 
      ref.get().onLocked(); 
     } 
    } 
} 

我」什麼在我的測試中看到的是it.next()in n otifyListeners()偶爾會引發ConcurrentModificationException。我的猜測是這是由訂閱者方法中的listeners.add()引起的。

我想我對這裏的迭代器有一個誤解。我假設迭代列表可以保護我免受由添加/刪除操作引起的併發問題的困擾。

顯然我錯了。是否迭代器只是在更改要迭代的集合時防止發生ConcurrentModificationException?例如,在迭代時調用列表中的remove()會引發錯誤,但調用它.remove()是安全的。

在我的情況下,訂閱調用add()在同一個列表中,因爲它正在迭代。我的理解是否正確?

+0

如果你閱讀了迭代器的文檔,它會告訴你,你不能修改底層結構 – efekctive

回答

2

如果我正確地讀了你的最後一句話,你的例子中的三個方法是從幾個線程同時調用的。如果確實如此,那麼這就是你的問題。

ArrayList是不是線程安全。無需額外的同步修改它就會導致未定義的行爲,無論您是直接修改還是使用迭代器。

您可以將對列表的訪問同步(例如,使三個方法同步),也可以使用像ConcurrentLinkedDeque這樣的線程安全的集合類。在後者的情況下,請確保閱讀JavaDoc(特別是關於每週一致的迭代器部分)以瞭解什麼是保證和什麼不是。

相關問題