如果在沒有足夠的同步的情況下使用ArrayList(例如),可能會出錯的有三個方面。
第一種情況是如果兩個線程同時發生更新ArrayList,那麼它可能會損壞。例如,附加到列表的邏輯是這樣的:
public void add(T element) {
if (!haveSpace(size + 1)) {
expand(size + 1);
}
elements[size] = element;
// HERE
size++;
}
現在假設我們有一個處理器/核心和兩個線程「同時」執行相同的列表上的該代碼。假設第一個線程到達標記爲HERE
的點並被搶佔。第二個線程出現,並覆蓋elements
中的第一個線程剛剛使用自己的元素更新的插槽,然後遞增size
。當第一個線程終於得到控制時,它將更新size
。最終的結果是我們添加了第二個線程的元素,而不是第一個線程的元素,並且很可能還會將null
添加到列表中。(這只是示意性的。在現實中,本地代碼編譯器可能重新排序的代碼,等等。但問題是,如果更新同時發生不好的事情都可能發生。)
的第二個場景的出現是由於主存儲器內容緩存在CPU的高速緩衝存儲器中。假設我們有兩個線程,一個添加元素到列表中,另一個讀取列表的大小。當線程添加一個元素時,它將更新列表的size
屬性。但是,由於size
不是volatile
,因此size
的新值可能不會立即寫入主存儲器。相反,它可以位於緩存中,直到Java內存模型要求刷新緩存寫入的同步點。同時,第二個線程可以在列表上調用size()
並獲得陳舊值size
。在最壞的情況下,第二個線程(例如調用get(int)
)可能會看到size
和elements
數組的值不一致,從而導致意外的異常。 (請注意,即使只有一個內核並且沒有內存緩存,也可能會發生這種問題。JIT編譯器可以自由使用CPU寄存器來緩存內存內容,並且這些寄存器不會因其內存位置而被刷新/刷新當發生線程上下文切換時)
第三種情況出現在您同步ArrayList
時的操作;例如通過將其包裝爲SynchronizedList
。
List list = Collections.synchronizedList(new ArrayList());
// Thread 1
List list2 = ...
for (Object element : list2) {
list.add(element);
}
// Thread 2
List list3 = ...
for (Object element : list) {
list3.add(element);
}
如果線程2的列表是一個ArrayList
或LinkedList
和兩個線程同時運行,線程2將失敗,ConcurrentModificationException
。如果是其他(家庭釀造)列表,則結果是不可預測的。問題在於製作list
同步列表不夠充分,以使它對不同線程執行的列表操作的序列是線程安全的。爲了實現這一點,應用程序通常需要在更高級別/更粗粒度的同步。
另外,我記得有人告訴我,多線程並沒有真正運行的同時,1個線程在運行了一段時間,並紛紛跟帖運行(計算機上有一個CPU)。
正確。如果只有一個內核可用來運行應用程序,顯然一次只能運行一個線程。這使得某些危害變得不可能,而其他危險發生的可能性也就越小。但是,操作系統可能會隨時在代碼中的任意位置從一個線程切換到另一個線程。
如果這是正確的,兩個線程怎麼能同時訪問相同的數據?也許線程1將在修改某些內容時停止,線程2將會啓動?
是。這是可能的。它發生的概率非常小,但這只是使這類問題更加陰險。
1 - 這是因爲線程時間分片的事件是極其罕見的,當上的硬件時鐘週期時間刻度測量。
字符串的一半和一半是不可能的,但如果有兩個線程將100個字符串添加到一個未同步的列表中,那麼最後可能不會獲得200個字符串。 – polygenelubricants 2010-08-28 03:28:59
是的。請參閱我的答案,瞭解爲什麼會發生這種情況。 – 2010-08-28 05:14:05
感謝您的詳細解釋和例子 – user433500 2010-08-28 20:37:24