互斥是因,同步是果;互斥是方法,同步是目的。java
synchronized
關鍵字是基本的互斥同步手段。它在編譯後會在同步代碼塊先後加入2條字節碼指令:monitorenter
和monitorexit
reference
類型的參數來指明要鎖定和解鎖的對象。若是Java程序中的synchronized
指定了對象參數,那就是這個對象的reference
;若是沒有指定,就根據synchronized
修飾的是實例方法仍是類方法,去取對應的對象實例或Class對象來做爲鎖對象。monitorenter
指令時,首先要嘗試獲取對象的鎖。若是這個對象沒被鎖定,或當前線程已經擁有了那個對象的鎖,把鎖的計數器加1;在執行monitorexit
指令時會將鎖計數器減1。當計數器爲0時,鎖就被釋放。若是獲取對象鎖失敗,那當前線程就要阻塞等待,直到對象鎖被另一個線程釋放爲止。synchronized
同步塊對同一條線程來講是可重入的,不會出現本身把本身鎖死的問題。重入鎖位於java.util.concurrent
包。基本用法和synchronized
類似,只是代碼寫法有區別:synchronized
是原生語法層面的實現。ReentrantLock
是API層面,使用lock()
和unlock()
方法配合try/finally
語句塊來實現。安全
重入鎖有3個高級特性:多線程
synchronized
中的鎖是非公平的,ReentrantLock
默認狀況下也是非公平的,但能夠經過帶布爾值的構造函數要求使用公平鎖。ReentrantLock
對象能夠同時綁定多個Condition
對象,而在synchronized
中,鎖對象的wait()
和notify()
或notifyAll()
方法能夠實現一個隱含的條件,若是要和多於一個的條件關聯的時候,就不得不額外地添加一個鎖,而ReentrantLock
則無須這樣作,只須要屢次調用newCondition()
方法便可。synchronized
的吞吐量隨着處理器數量增長而降低得很是嚴重。synchronized
方式。互斥同步最主要的問題就是進行線程阻塞和喚醒所帶來的性能問題,所以這種同步也稱爲阻塞同步(Blocking Synchronization)。併發
按處理問題的方式來講:函數
CAS操做: CAS指令須要有3個操做數,分別是內存位置(在Java中能夠簡單理解爲變量的內存地址,用V表示)、舊的預期值(用A表示)和新值(用B表示)。CAS指令執行時,當且僅當V符合舊預期值A時,處理器用新值B更新V的值,不然它就不執行更新,可是不管是否更新了V的值,都會返回V的舊值,這個處理過程是個原子操做。性能
ABA問題: 若是一個變量V初次讀取的時候是A值,而且在準備賦值的時候檢查到它仍然爲A值,那咱們就能說它的值沒有被其餘線程改變過了嗎?若是在這段期間它的值曾經被改爲了B,後來又被改回爲A,那CAS操做就會誤認爲它歷來沒有被改變過。優化
若是一個方法原本就不涉及共享數據,那它就無須任何同步措施去保證正確性。操作系統
線程阻塞的時候,讓等待的線程不放棄cpu執行時間,而是執行一個自旋(通常是空循環),這叫作自旋鎖。線程
自旋等待自己雖然避免了線程切換的開銷,但它是要佔用處理器時間的,所以,若是鎖被佔用的時間很短,自旋等待的效果就很是好,反之,若是鎖被佔用的時間很長,那麼自旋的線程只會白白消耗處理器資源,帶來性能上的浪費。指針
所以,自旋等待的時間必需要有必定的限度。若是自旋超過了限定的次數仍然沒有成功得到鎖,就應當使用傳統的方式去掛起線程了。自旋次數的默認值是10次,用戶可使用參數-XX:PreBlockSpin
來更改。
JDK1.6引入了自適應的自旋鎖。自適應意味着自旋的時間再也不固定了,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。好比前一次自旋了3次就得到了一個鎖,那麼下一次虛擬機會容許他自旋更屢次來得到這個鎖。若是一個鎖不多能經過自旋成功得到,那麼以後再遇到這個狀況就會省略自旋過程了。
虛擬機即時編譯器在運行時,對一些代碼上要求同步,可是被檢測到不可能存在共享數據競爭的鎖進行消除。通常根據逃逸分析的數據支持來做爲斷定依據。
原則上,咱們在編寫代碼的時候,老是推薦將同步塊的做用範圍限制得儘可能小——只在共享數據的實際做用域中才進行同步,這樣是爲了使須要同步的操做數量儘量變小,若是存在鎖競爭,那等待鎖的線程也能儘快拿到鎖。
但若是一系列操做頻繁對同一個對象加鎖解鎖,或者加鎖操做再循環體內,會耗費性能,這時虛擬機會擴大加鎖範圍。
輕量級鎖是JDK 1.6之中加入的新型鎖機制。它的做用是在沒有多線程競爭的前提下,減小傳統的重量級鎖使用操做系統互斥量產生的性能消耗。
HotSpot虛擬機的對象頭(Object Header)分爲兩部分信息,第一部分用於存儲對象自身的運行時數據,這部分稱爲Mark Word
。還有一部分存儲指向方法區對象類型數據的指針。
在代碼進入同步塊的時候,若是此同步對象沒有被鎖定(鎖標誌位爲「01」狀態),虛擬機首先將在當前線程的棧幀中創建一個名爲鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的Mark Word的拷貝(官方把這份拷貝加了一個Displaced前綴,即Displaced Mark Word)。而後,虛擬機將使用CAS操做嘗試將對象的Mark Word更新爲指向Lock Record的指針。若是這個更新動做成功,那麼這個線程就擁有了該對象的鎖,而且對象Mark Word的鎖標誌位(Mark Word的最後2bit)將轉變爲「00」,即表示此對象處於輕量級鎖定狀態。若是這個更新操做失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀,若是是說明當前線程已經擁有了這個對象的鎖,那就能夠直接進入同步塊繼續執行,不然說明這個鎖對象已經被其餘線程搶佔了。若是有兩條以上的線程爭用同一個鎖,那輕量級鎖就再也不有效,要膨脹爲重量級鎖,鎖標誌的狀態值變爲「10」,Mark Word中存儲的就是指向重量級鎖(互斥量)的指針,後面等待鎖的線程也要進入阻塞狀態。
解鎖過程也是經過CAS操做來進行的。若是對象的Mark Word仍然指向着線程的鎖記錄,那就用CAS操做把對象當前的Mark Word和線程中複製的Displaced Mark Word替換回來,若是替換成功,整個同步過程就完成了。若是替換失敗,說明有其餘線程嘗試過獲取該鎖,那就要在釋放鎖的同時,喚醒被掛起的線程。
沒有鎖競爭時,輕量級鎖用CAS操做替代互斥量的開銷,性能較優。有鎖競爭時,除了互斥量開銷,還有CAS操做開銷,因此性能較差。可是,通常狀況下,在整個同步週期內都是不存在競爭的」,這是一個經驗數據。
偏向鎖也是JDK1.6中引入的鎖優化,它的目的是消除數據在無競爭狀況下的同步原語,進一步提升程序的運行性能。若是說輕量級鎖是在無競爭的狀況下使用CAS操做去消除同步使用的互斥量,那偏向鎖就是在無競爭的狀況下把整個同步都消除掉,連CAS操做都不作了。
當鎖對象第一次被線程獲取的時候,虛擬機將會把對象頭中的標誌位設爲「01」,即偏向模式。同時使用CAS操做把獲取到這個鎖的線程的ID記錄在對象的Mark Word之中,若是CAS操做成功,持有偏向鎖的線程之後每次進入這個鎖相關的同步塊時,虛擬機均可以再也不進行任何同步操做。當有另一個線程去嘗試獲取這個鎖時,偏向模式結束。
偏向鎖能夠提升帶有同步但無競爭的程序性能,但並不必定老是對程序運行有利。若是程序中大多數的鎖老是被多個不一樣的線程訪問,那偏向模式就是多餘的。在具體問題具體分析的前提下,有時候使用參數-XX:-UseBiasedLocking
來禁止偏向鎖優化反而能夠提高性能。