併發編程知識點全面總結

 

 

爲何會出現鎖

 

死鎖如何產生:java

1.對共享資源競爭node

2.進程推動順序不當數據庫

 

產生死鎖的必要條件:緩存

1.循環等待:session

2.不可剝奪:進程已得到的資源,在未使用以前,不能強行剝奪數據結構

3.佔有並請求:框架

4.互斥:jvm

 

鎖的類型以及相關概念

 

講到鎖,就得先講一下同步,每當咱們要用同步是就會想到synchronized,函數

那麼java線程同步的方式其實有五種:工具

1.同步方法,synchronized修飾方法

2.同步代碼塊,synchronized修飾語句塊

3.使用特殊域變量(volatile)實現線程同步

4.使用ReentrantLock來實現線程同步

5.使用局域變量來實現線程同步,若是使用ThreadLocal來管理變量,那麼每個使用該變量的線程都會得到該變量的副本,副本之間是相互獨立的,這樣每一個線程修改本身變量的時候不會影響其餘線程。

 

synchronized咱們用起來是同步的做用,其實內部是鎖來實現的,具體來看看synchronized究竟是什麼

 

synchronized:

synchronized能夠保證方法或者代碼塊在運行時,同一時刻只有一個方法能夠進入到臨界區,同時它還能夠保證共享變量的內存可見性

當被synchronized修飾時,synchronized會鎖定當前變量,只有當前線程可以訪問該變量,其餘線程會被阻塞。

synchronized可使用在變量和方法中

synchronized能夠實現變量的修改可見性和原子性

 

synchronized的具體是怎麼實現的呢?

首先來看兩個概念Java對象頭monitor

Java對象頭和monitor是實現synchronized的基礎

synchronized用的鎖就是存在Java對象頭

對象頭

Hotspot的對象頭主要包括兩部分,Mark Word(標記字段),Klass Pointer(類型指針)。

Klass Pointer:對象指向類元數據的指針,虛擬機經過這個來肯定對象是那個類的實例

Mark Word:存儲自身運行時數據,好比哈希碼,GC分代年齡,鎖標記位,線程持有的鎖等

Mark Word它是實現輕量級鎖和偏向鎖的關鍵。

Monitor

什麼是Monitor?咱們能夠把它理解爲一個同步工具,也能夠描述爲一種同步機制,它一般被描述爲一個對象。

與一切皆對象同樣,全部的Java對象是天生的Monitor,每個Java對象都有成爲Monitor的潛質,由於在Java的設計中 ,每個Java對象自打孃胎裏出來就帶了一把看不見的鎖,它叫作內部鎖或者Monitor鎖。

Monitor 是線程私有的數據結構,每個線程都有一個可用monitor record列表,同時還有一個全局的可用列表。每個被鎖住的對象都會和一個monitor關聯(對象頭的MarkWord中的LockWord指向monitor的起始地址),同時monitor中有一個Owner字段存放擁有該鎖的線程的惟一標識,表示該鎖被這個線程佔用。

 

如今回到synchornized代碼實現上

當被synchronized關鍵字修飾的代碼塊在被編譯成字節碼的時候,會在該代碼塊開始和結束的地方加入 monitorenter 和 moniterexist 指令,任何對象都有一個monitor相關聯,當一個monitor被持有後,他就處於鎖定狀態,當線程執行到monitorenter指令時,會獲嘗試獲取對象對應的monitor的全部權,即獲取對象的鎖。

虛擬機在執行這兩個命令的時候,會檢查對象的鎖狀態是否爲空或當前線程是否已經擁有對象的鎖,

若是是 則對象鎖的計數器加 1 ,直接進入同步代碼,

若是不是,則當前線程阻塞等待,等待鎖釋放。

 

synchornized相似的還有Volatile,Lock,ThreadLocal

Volatile:

volatile,final,synchronized均可以實現可見性

使用volatile要知足的條件:

1.運算結果只保證一個線程修改變量的值或者不依賴於變量的當前值

2.不須要與其餘狀態變量參與不變的約束

3.訪問變量時不須要加鎖

 

Volatile的不變性:

1.當前處理器緩存行的數據會寫回到系統內存

2.這個緩存操做會引發其餘cpu中緩存該內存地址的數據失效

Volatile禁止指令重排序,由於Volatile變量在賦值後會有一個lock add命令,這個命令至關於內存屏障,重排序時不能把屏障後的指令重排序到屏障以前。

 

Volatile保證可見性:add指令會使得其餘工做線程的工做內存緩存的數據失效

 

synchronized與Volatile的區別:

volatile,final,synchronized均可以實現可見性

volatile的本質是告訴jvm這個變量是在寄存器中的值是不肯定的,須要從主存中讀取。

synchronized是鎖定當前變量,只有當前線程可以訪問該變量,其餘線程被阻塞。

 

volatile僅能實現變量的修改可見性,不具有原子性,而synchronized均可以保證

volatile標記的變量不會被編譯器優化,而synchronized能夠

volatile使用時變量級別,而synchronized可使用在變量和方法

volatile不會阻塞線程,而synchronized可能會

 

 

Condition:

condition將Object監視器方法(wait,notify和notifyAll),分解成了多個不一樣的對象,而後經過這些對象和任意Lock進行結合使用。

Lock就至關於替代了synchronized方法和語句的使用。

condition替代了Objective監視器的使用方法。

condition是java5之後出現的機制,一個對象能夠有多個condition(對象監視器),線程能夠註冊在不一樣的condition,能夠更靈活的調度線程,有選擇的調度線程,而synchronized至關於只有一個condition,全部的線程都註冊在他身上,線程調度得調度全部的註冊線程。

 

Lock:

Lock是使用condition進行線程之間的調度的。

Lock的鎖定是經過代碼實現的,而synchronized是在JVM層面實現。

Lock提供的是一種顯示的,可輪詢的定時的以及可中斷的鎖獲取操做。

 

synchronized與Lock的區別:

1.Lock是一個接口,而synchronized是關鍵字,是在jvm層面實現的

2.Lock發生異常時,若是沒有unlock()釋放鎖,會形成死鎖現象,須要在finally塊中釋放,synchronized發生異常時會自動釋放線程佔有的鎖

3.Lock可讓等待鎖的線程相應中斷,他不行,他得一直等待下去,不能中斷

4.Lock能夠知道是否成功獲取鎖,而他不行

5.Lock能夠提升多個線程的讀效率

Lock的鎖定是經過代碼實現的,而synchronized是在JVM層面實現。

適用場景:

若是競爭資源不激烈,二者性能差很少,當有大量線程同時競爭時,Lock的性能高於synchronized。

 

AQS(AbstractQueuedSynchronizer):

提供了一個基於FIFO隊列,能夠用於建阻塞鎖或者同步容器的基礎框架,AQS基於FIFO隊列實現,存在一個個節點,node就是一個節點。對於FIFO中隊列的各類操做在AQS中已經實現,AQS的子類只須要重寫 tryAcquire(int arg) 和 tryRelease(int arg) 方法,用int值表示狀態,子類必須定義更改此狀態的受保護方法,並定義那種狀態表示被獲取或者被釋放。這些都實現後,此類的其餘方法就能夠實現全部排隊和阻塞機制。

tryAcquire(int arg) 試圖在獨佔模式下獲取對象狀態。

acquire() 以獨佔模式獲取對象,忽略中斷。

tryRelease(int arg) 試圖設置狀態來反映獨佔模式下的下一個釋放。

release()以獨佔模式釋放對象

tryAcquireShared(int arg)試圖在共享模式下獲取對象狀態。

tryReleaseShared(int arg)試圖設置狀態反應共享模式下的一個釋放     

 ReentrantLock:

ReentrantLock是java.utils.locks的一個可重入鎖類,在高競爭的狀況下有良好的性能,能夠中斷支持重人性,即對公享資源能夠反覆加鎖,當前線程再次獲取該鎖時,不會被堵塞,ReentrantLock是基於AQS實現,而AQS又是基於FIFO實現的,整個AQS實際上是模板模式的經典應用,FIFO隊列中全部的操做,AQS已經實現,AQS的子類只須要重寫tryAcquire和tryRelese。

ReentrantLock中有一個抽象類syc繼承與AbstractQuenedSynchronizer,syc的實現類FairSync,NoFairSync,即公平鎖,非公平鎖,他們都是ReentrantLock的靜態內部類。ReentrantLock中用的比較多的是非公平鎖。

非公平鎖流程,線程1調用ReentrantLock的lock(),線程一變成獨佔,第一個線程作了兩件事

1.設置AbstractQuenedSynchronizer的satae爲1

2.設置AbstractQuenedSynchronizer的thread爲當前線程

這樣當第二個線程獲取鎖時,就執行else,調用acquire方法,進而調用acquire中的tryacquire。

公平鎖:按照時間順序,先等待的線程,先獲得鎖,公平鎖不會產生飢餓鎖,只要排隊最終都能得到鎖

非公平性鎖則不必定,有可能剛釋放鎖的線程能再次獲取到鎖。

 

ReentrantLock使用場景:

1.發現子程序正在運行,能夠再次進入並執行

2.須要使用中斷鎖

3.嘗試等待執行,就是發現操做已經在執行,嘗試一段時間後,等待超時就不執行

4.發現操做已經在執行,就不執行了c

 

ThreadLocal

ThreadLoacl爲每個線程建立了一個獨立的變量副本,這樣每一個線程均可以獨立改變本身的副本,操做變量時互不影響。這塊與線程同步機制不一樣,線程同步機制是多個線程共享一個變量。製造變量副本是會消耗內存的,因此他們不一樣之處能夠說ThreadLoacl其實是用空間換取時間策略,而線程同步是時間換取空間策略。

ThreadLoaclMap是ThreadLoacl的一個靜態內部類,它是實現線程隔離機制的關鍵

ThreadLocalMap提供了一種用鍵值對方式存儲每個線程的變量副本的方法,key爲當前ThreadLocal對象,value則是對應線程的變量副本。

 

ThreadLocal使用場景:

數據庫鏈接,session管理

好比在數據庫鏈接池中,有多個DAO要獲取鏈接,但這時須要完成事務,只能這些DAO獲取同一個Connection,這樣才能完成一個事務。從ThreadLoacl中獲取Connetion的話,Dao就會被列入同一個Connection.

 

synchronized與ThreadLocal

能夠說ThreadLoacl用於線程間數據隔離,而synchronized是線程間數據共享。

 

鎖升級:

無鎖 --> 偏向鎖 --> 輕量級鎖(利用CAS原理,避免重量級鎖的消耗) --> 重量級鎖

注意:鎖能夠升級,可是不能降級,爲了減小得到鎖和釋放鎖所帶來的消耗

 

jdk1.6對鎖的優化:

1.6中出現了輕量級鎖,偏向鎖,鎖消除,適應性自旋鎖,鎖粗化開啓,這些操做都是爲了線程之間更高效的共享數據,解決競爭問題,主要是優化synchronized中獲取鎖,釋放鎖的性能問題。

 

jdk1.8新特性:

1.容許爲接口添加一個非抽象的方法實現,使用default方法,叫擴展方法

2.Lambda表達式,匿名函數

3.方法與構造函數引用

4.函數式接口

 

輕量級鎖實現:

輕量級鎖的引入爲了提高在沒有提高線程競爭的狀況下,執行同步代碼的效率。

虛擬機使用CAS操做將對象頭中的Mark Word 更新爲當前線程的Lock Word的指針

若是更新成功,表示已經持有這個鎖,mark Word的標記位 00,爲輕量級鎖

若是更新失敗,虛擬機會檢查對象中的Mark Word是否指向當前線程的棧幀,若是指向則直接進入同步代碼直接執行,不是,說明線程競爭。若是有兩條以上的線程搶佔資源,輕量級鎖膨脹爲重量級鎖,鎖狀態改成10,mark Word中存儲指向重量級鎖的指針,後面的鎖進入阻塞狀態。

輕量級性能提高的依據就是「對於大多數鎖」在整個同步週期是不存在競爭的,沒有競爭時輕量級鎖利用CAS操做避免使用系統互斥量的開銷。

 

偏向鎖實現:

當只有一個線程執行同步塊時,這種狀況下,使用輕量級鎖也會有多個CAS操做。因此開啓偏向鎖後,虛擬機檢查當前線程是否處於無鎖狀態01,且標記爲0沒有偏向鎖,若是成功,線程會用CAS操做把鎖的線程ID放入對象的Mark Word中,之後有偏向鎖的線程進入同步塊時,虛擬機只看鎖ID是否同樣,若是是直接進入,不用CAS操做了。若是有其餘線程獲取該對象的鎖,偏向模式就結束。根據鎖的狀態,撤銷偏向鎖後看恢復成無鎖仍是偏向鎖。如同輕量級鎖的介紹。

輪詢鎖:

不能同時得到全部鎖時,可使用輪詢鎖或者定時鎖避免死鎖。當一個線程得到多個鎖時,已經得到一部分,另外一部分沒得到,此時返回失敗,釋放已經得到的鎖,從新嘗試得到全部的鎖。

 

樂觀鎖與悲觀鎖:

每次拿數據時,都認爲別人不會去修改,因此不上鎖,更新時採起判斷別人更改數據了沒。

jdk中對樂觀鎖的實現就是CAS

分爲三步驟:獲取數據,寫入校驗,數據寫入

適用於寫比較少的狀況,衝突發生的不多,減小系統開銷,增大系統的吞吐量。

樂觀鎖大可能是基於數據版本記錄機制實現的,讀取數據時,將版本號,一同讀出,以後更新版本號加一,版本數據在於數據庫表的版本信息比對,若是提交的數據版本號大於數據庫當前版本,則給予更新,不然認爲是過時

相關文章
相關標籤/搜索