什麼是線程安全?
如果你的代碼所在的程序中有多個線程在同時運作,而這些線程可能會同時運作這段代碼。如果每次運作結果和單線程運作的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
或者說:一個類或者程式所提供的接口對于線程來說是原子操作或者多個線程之間的切換不會導緻該接口的執行結果存在二義性,也就是說我們不用考慮同步的問題。
線程安全問題都是由全局變量及靜态變量引起的。
若每個線程中對全局變量、靜态變量隻有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則就可能影響線程安全。
舉例
比如一個 ArrayList 類,在添加一個元素的時候,它可能會有兩步來完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
在單線程運作的情況下,如果 Size = 0,添加一個元素後,此元素在位置 0,而且 Size=1;
那好,現在我們來看看 ArrayList 的情況,元素實際上隻有一個,存放在位置 0,而 Size 卻等于 2。這就是“線程不安全”了。
線程安全性
類要成為線程安全的,首先必須在單線程環境中有正确的行為。如果一個類實作正确(這是說它符合規格說明的另一種方式),那麼沒有一種對這個類的對象的操作序列(讀或者寫公共字段以及調用公共方法)可以讓對象處于無效狀态,觀察到對象處于無效狀态、或者違反類的任何不可變量、前置條件或者後置條件的情況。
此外,一個類要成為線程安全的,在被多個線程通路時,不管運作時環境執行這些線程有什麼樣的時序安排或者交錯,它必須仍然有如上所述的正确行為,并且在調用的代碼中沒有任何額外的同步。其效果就是,在所有線程看來,對于線程安全對象的操作是以固定的、全局一緻的順序發生的。
正确性與線程安全性之間的關系非常類似于在描述 ACID(原子性、一緻性、獨立性和持久性)事務時使用的一緻性與獨立性之間的關系:從特定線程的角度看,由不同線程所執行的對象操作是先後(雖然順序不定)而不是并行執行的。
線程安全程度
線程安全性不是一個非真即假的命題。 Vector 的方法都是同步的,并且 Vector 明确地設計為在多線程環境中工作。但是它的線程安全性是有限制的,即在某些方法之間有狀态依賴(類似地,如果在疊代過程中 Vector 被其他線程修改,那麼由 Vector.iterator() 傳回的 iterator會抛出ConcurrentModifiicationException)。
Bloch 給出了描述五類線程安全性的分類方法:不可變、線程安全、有條件線程安全、線程相容和線程對立。隻要明确地記錄下線程安全特性,那麼您是否使用這種系統都沒關系。這種系統有其局限性 -- 各類之間的界線不是百分之百地明确,而且有些情況它沒照顧到 -- 但是這套系統是一個很好的起點。這種分類系統的核心是調用者是否可以或者必須用外部同步包圍操作(或者一系列操作)。下面幾節分别描述了線程安全性的這五種類别。
線程安全的對象具有在上面“線程安全”一節中描述的屬性 -- 由類的規格說明所規定的限制在對象被多個線程通路時仍然有效,不管運作時環境如何排列,線程都不需要任何額外的同步。這種線程安全性保證是很嚴格的 -- 許多類,如 Hashtable 或者 Vector 都不能滿足這種嚴格的定義。
如果對一個有條件線程安全類進行記錄,那麼您應該不僅要記錄它是有條件線程安全的,而且還要記錄必須防止哪些操作序列的并發通路。使用者可以合理地假設其他操作序列不需要任何額外的同步。
線程相容類不是線程安全的,但是可以通過正确使用同步而在并發環境中安全地使用。這可能意味着用一個 synchronized 塊包圍每一個方法調用,或者建立一個包裝器對象,其中每一個方法都是同步的(就像 Collections.synchronizedList() 一樣)。也可能意味着用 synchronized 塊包圍某些操作序列。為了最大程度地利用線程相容類,如果所有調用都使用同一個塊,那麼就不應該要求調用者對該塊同步。這樣做會使線程相容的對象作為變量執行個體包含在其他線程安全的對象中,進而可以利用其所有者對象的同步。
許多常見的類是線程相容的,如集合類 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 類 Connection 和 ResultSet 。
線程對立類是那些不管是否調用了外部同步都不能在并發使用時安全地呈現的類。線程對立很少見,當類修改靜态資料,而靜态資料會影響在其他線程中執行的其他類的行為,這時通常會出現線程對立。線程對立類的一個例子是調用 System.setOut() 的類。