本博客系列是學習併發編程過程當中的記錄總結。因爲文章比較多,寫的時間也比較散,因此我整理了個目錄貼(傳送門),方便查閱。html
併發編程系列博客傳送門java
鎖從宏觀上來分類,能夠分爲悲觀鎖與樂觀鎖。注意,這裏說的的鎖能夠是數據庫中的鎖,也能夠是Java等開發語言中的鎖技術。悲觀鎖和樂觀鎖其實只是一類概念(對某類具體鎖的總稱),不是某種語言或是某個技術獨有的鎖技術。數據庫
樂觀鎖是一種樂觀思想,即認爲讀多寫少,遇到併發寫的可能性低,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,可是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,採起在寫時先讀出當前版本號,而後加鎖操做(比較跟上一次的版本號,若是同樣則更新),若是失敗則要重複讀-比較-寫的操做。java中的樂觀鎖基本都是經過CAS操做實現的,CAS是一種更新的原子操做,比較當前值跟傳入值是否同樣,同樣則更新,不然失敗。數據庫中的共享鎖也是一種樂觀鎖。編程
悲觀鎖是就是悲觀思想,即認爲寫多,遇到併發寫的可能性高,每次去拿數據的時候都認爲別人會修改,因此每次在讀寫數據的時候都會上鎖,這樣別人想讀寫這個數據就會block直到拿到鎖。java中典型的悲觀鎖就是Synchronized,AQS框架下的鎖則是先嚐試cas樂觀鎖去獲取鎖,獲取不到,纔會轉換爲悲觀鎖,如ReentrantLock。數據庫中的排他鎖也是一種悲觀鎖。多線程
根據線程獲取鎖的搶佔機制,鎖能夠分爲公平鎖和非公平鎖,公平鎖表示線程獲取鎖的順序是按照線程請求鎖的時間遲早來決定的,也就是最先請求鎖的線程將最先獲取到鎖。而非公平鎖則在運行時闖入,也就是先來不必定先得。併發
ReentrantLock提供了公平和非公平鎖的實現。框架
例如,假設線程A已經持有了鎖,這時候線程B請求該鎖其將會被掛起。當線程A釋放鎖後,假如當前有線程C也須要獲取該鎖,若是採用非公平鎖方式,則根據線程調度策略,線程B和線程C二者之一可能獲取鎖,這時候不須要任何其餘干涉,而若是使用公平鎖則須要把C掛起,讓B獲取當前鎖。函數
在沒有公平性需求的前提下儘可能使用非公平鎖,由於公平鎖會帶來性能開銷。性能
根據鎖只能被單個線程持有仍是能被多個線程共同持有,鎖能夠分爲獨佔鎖和共享鎖。學習
獨佔鎖保證任什麼時候候都只有一個線程能獲得鎖,ReentrantLock就是以獨佔方式實現的。共享鎖則能夠同時由多個線程持有,例如ReadWriteLock讀寫鎖,它容許一個資源能夠被多線程同時進行讀操做。
獨佔鎖是一種悲觀鎖,因爲每次訪問資源都先加上互斥鎖,這限制了併發性,由於讀操做並不會影響數據的一致性,而獨佔鎖只容許在同一時間由一個線程讀取數據,其餘線程必須等待當前線程釋放鎖才能進行讀取。
共享鎖則是一種樂觀鎖,它放寬了加鎖的條件,容許多個線程同時進行讀操做。
當一個線程要獲取一個被其餘線程持有的獨佔鎖時,該線程會被阻塞,那麼當一個線程再次獲取它本身已經獲取的鎖時是否會被阻塞呢?若是不被阻塞,那麼咱們說該鎖是可重入的,也就是隻要該線程獲取了該鎖,那麼能夠無限次數(嚴格來講是有限次數)地進入被該鎖鎖住的代碼。
public synchronized void helloA(){ ... } public synchronized void helloB(){ ... helloA(); }
在如上代碼中,調用helloB方法前會先獲取內置鎖,而後打印輸出。以後調用helloA方法,在調用前會先去獲取內置鎖,若是內置鎖不是可重入的,那麼調用線程將會一直被阻塞。
實際上,synchronized內部鎖是可重入鎖。可重入鎖的原理是在鎖內部維護一個線程標示,用來標示該鎖目前被哪一個線程佔用,而後關聯一個計數器。一開始計數器值爲0,說明該鎖沒有被任何線程佔用。當一個線程獲取了該鎖時,計數器的值會變成1,這時其餘線程再來獲取該鎖時會發現鎖的全部者不是本身而被阻塞掛起。
可是當獲取了該鎖的線程再次獲取鎖時發現鎖擁有者是本身,就會把計數器值加+1,當釋放鎖後計數器值-1。當計數器值爲0時,鎖裏面的線程標示被重置爲null,這時候被阻塞的線程會被喚醒來競爭獲取該鎖。
因爲Java中的線程是與操做系統中的線程一一對應的,因此當一個線程在獲取鎖(好比獨佔鎖)失敗後,會被切換到內核狀態而被掛起。當該線程獲取到鎖時又須要將其切換到內核狀態而喚醒該線程。而從用戶狀態切換到內核狀態的開銷是比較大的,在必定程度上會影響併發性能。自旋鎖則是,當前線程在獲取鎖時,若是發現鎖已經被其餘線程佔有,它不立刻阻塞本身,在不放棄CPU使用權的狀況下,屢次嘗試獲取(默認次數是10,可使用-XX:PreBlockSpinsh參數設置該值),頗有可能在後面幾回嘗試中其餘線程已經釋放了鎖。若是嘗試指定的次數後仍沒有獲取到鎖則當前線程纔會被阻塞掛起。由此看來自旋鎖是使用CPU時間換取線程阻塞與調度的開銷,可是頗有可能這些CPU時間白白浪費了。