本文摘選自深入理解Java虛擬機。
方法一:互斥同步(Matual Exclusion & Synchronization)
互斥同步屬於一種悲觀的併發策略,總是認爲只要不去做正確的同步措施,就肯定會出現問題。無論共享數據是否出現競爭,它都要進行加鎖。故稱互斥同步的鎖爲悲觀鎖
①使用synchronized關鍵字。synchronized同步代碼塊對同一個線程來說是可重入的,不會出現自己把自己鎖死的問題。
②使用ReentrantLock重入鎖來實現同步。ReentrantLock與synchronized很相似。都是對同一個線程可重入。不過ReentrantLock增加了一些高級的功能:等待可中斷,可實現公平鎖。鎖可以綁定多個條件。
等待可中斷是指:當持有鎖的線程長期不釋放鎖的時候,正在等待的線程可以選擇放棄等待。改爲處理其他事情,可中斷特性對處理執行時間非常長的同步塊很有幫助。
公平鎖:多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖。而非公平鎖不保證這一點:在鎖被釋放的時候,任何一個等待鎖的線程都有機會獲得鎖(通過競爭獲得鎖)。synchronized中的鎖是非公平鎖,ReentrantLock默認情況下也是非公平鎖,但是可以通過帶布爾值的構造函數來構造公平鎖。
鎖綁定多個條件是指一個ReentrantLock對象可以同時綁定多個Condition對象。在synchronized中,隱含着綁定一個對象,如果要是有多個對象關聯的時候,就要多加一個額外的鎖。ReentrantLock則不需要這樣做,只需要多次調用newCondition()方法即可。
方法二:非阻塞同步(又稱樂觀鎖)
互斥同步最主要的問題就是進行線程阻塞和喚醒所帶來的性能問題,因此這種同步也稱爲阻塞同步(Blocking Synchronization)。所以就有了一種不需把線程掛起的同步操作稱之爲非阻塞同步(Non-Blocking Synchronization):一種基於衝突檢測的樂觀併發策略,通俗了說,就是先進行操作,如果沒有其他線程爭用共享數據,那操作就成功了;如果共享數據有爭用,產生了衝突,那就採用其他的補償措施(最常見的的就是不斷重試,直到成功爲止)。在Java中常見的就是Compare-and Swap(CAS)指令。CAS指令有三個操作數,分別是內存位置的值(用V表示),舊的預期值A,新值B,CAS指令執行的時候,當且僅當V符合舊預期值A時,處理器用新值B更新V的值,否則 就不執行更新,無論是否更新V的值,都會返回V的舊值。
CAS指令這個執行過程是個原子操作。其他的原子操作還有指令:測試並設置(Test- And-Set)、獲取並增加(Fetch-And-Increment)、交換(Swap)、加載鏈接/條件存儲(Load-Linked/Store-Conditional)。
方法三:無同步方案
如果一個方法本來就不涉及共享數據,那它自然就不需要任何同步措施去保證正確性,因此會有一些代碼天生就是線程安全的。
一類:可重入代碼(Reentrant Code):這種代碼叫做純代碼,可以在代碼執行的任何時刻去中斷它,轉而去執行另一段代碼,而在控制權返回後,原來的程序不會出現任何錯誤。判斷是否可重入性的原則:如果一個方法,他的返回結果是可以預測的,只要輸入了相同的數據,就能返回相同的結果,那它就滿足可重入性的要求。
二類:線程本地存儲(Thread Local Storage):如果一段代碼中所需要的數據必須與其他代碼共享數據。如果能保證這些共享數據的代碼在同一個線程中執行,那麼這些共享數據的可見範圍在同一個線程之內。這樣不需要同步也能保證線程之間不出現數據爭用的問題。在Java中,通過java.lang.ThreadLocal 類來實現線程本地存儲的功能。