爲何會出現鎖
死鎖如何產生: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
分爲三步驟:獲取數據,寫入校驗,數據寫入
適用於寫比較少的狀況,衝突發生的不多,減小系統開銷,增大系統的吞吐量。
樂觀鎖大可能是基於數據版本記錄機制實現的,讀取數據時,將版本號,一同讀出,以後更新版本號加一,版本數據在於數據庫表的版本信息比對,若是提交的數據版本號大於數據庫當前版本,則給予更新,不然認爲是過時