1.消失的請求數
當我們要計算一個類中的某個函數被調用的次數時,用單線程跑絕對沒問題,但是放到多線程中就會出事。
因爲 count ++ 的操作過程是:讀取->修改->寫入
假設線程A和線程B同時調用了函數,那麼他們讀取count的值是一樣的,接着增加數值,最後兩個線程寫入的數值都是一樣的,造成少增加一次的錯誤。
2.構造出兩份相同的對象
構造函數私有化,我們會寫一個公用返回參數爲對象的函數,用於返回一個new出來的對象。
當兩個線程同時調用這個函數時,就會產生返回兩個相同的對象,即兩個地方公用一個對象的錯誤。
3.對象逸出
在一個構造函數中:1)接收一個事件源,添加一個監聽器,任務是觸發時,就操作this屬性。2)對屬性的初始化(new)
當線程A調用構造函數,還未對屬性進行初始化時,線程B恰好也調用了構造函數,觸發了事件源,由於線程A已經往事件源註冊了監聽器,因此線程B會觸發任務,操作this的屬性
此時的this屬性還未初始化,會報空指針異常。
4.半成品
對象逸出是指不想發佈對象,卻不小心發佈了。想發佈對象,卻在對象還沒製造好之前,就給了對方使用半成品的機會。
這四個例子,分別對應三種常見的線程不安全情形:
- 讀取-修改-寫入: 對應上面“消失的請求數”的例子
- 先檢查後執行:對應上面“意外懷孕”的例子
- 發佈未完整構造的對象:對應上面“考題泄漏”和“半成品”兩個例子
絕大多數的線程不安全問題,都可以歸結爲這三種情形。而這三種情形,其實又可以再縮減爲兩種:對象創建時和對象創建後。不僅僅是在對象創建後的業務邏輯中要考慮線程的安全性,在對象創建的過程中,也要考慮線程安全。