鎖是在開發的過程沒法避免的問題。也是面試常問的問題。 本章比較詳細的解決了java中的鎖,記住是鎖。html
PS: 下面內容測試的結果不是十分正確。第一,測試的jdk是1.6,而不是1.8.測試的沒有關閉UseBiasedLocking(偏向鎖)java
多種強度的鎖方式。優先使用性能消耗低的方式,噹噹前方式不可用或者不使用,改變成消耗比當前大的鎖方式。 是一種十分靈活,智能的性能優化方案,對非特定場景十分適合。不須要開發者關注鎖優化,只須要關注業務程序員
同一個鎖, 重入是應付鎖複雜使用狀況下的一把利器,是鎖性能重點。a與b方式使用同一個鎖。a調用b,須要兩個鎖指令,而重入的解決方案是a執行鎖指令,當a調用b方法的時候,不使用鎖指令。那麼鎖的開銷減小一半。重入得越多鎖開銷減小越多。 有測試哪裏,須要你們本身測試。 下面的連接中性能測試其實已經給了答案,不知道哪位大神,能夠把答案告訴打雜的老胡面試
分讀寫鎖,讀鎖與讀鎖之間能夠共享。在讀多寫少的場景,提升鎖的性能 下面博客有對讀寫的性能測試:http://www.inter12.org/archives/292spring
爲了性能,不按上鎖的循序得到鎖,即不公平鎖。按照上鎖的循序得到鎖,即公平鎖。 公平不是爲了優先級 下面博客有對公平的性能測試:https://yq.aliyun.com/articles/48612sql
不用手動調用unLock系列釋放鎖的方法。解決在複雜的開發體系(業務複雜,開發人員能力良莠不齊,細節無視與混淆,測試困難)中,鎖操做問題。 異常釋放 誰來釋放數據庫
當其餘線程得到鎖以後,等待固定時間以後,尚未得到鎖就不在爭奪鎖。json
synchronized | StampedLock | ReentrantLock | ReentrantReadWriteLock | |
---|---|---|---|---|
鎖升級 | 支持 | 支持 | 不支持 | 不支持 |
重入 | 支持 | 不支持 | 可支持 | 可支持 |
讀寫 | 不支持 | 支持 | 不支持 | 支持 |
公平 | 不支持 | 不支持 | 支持 | 支持 |
自動釋放 | 支持 | 不支持 | 不支持 | 不支持 |
鎖等待超時 | 不支持 | 支持 | 支持 | 支持 |
線程中斷 | 不支持 | 支持 | 支持 | 支持 |
實際使用 | 簡單首選 | 不建議使用 | 比較簡單 | 當心使用 |
synchronized | StampedLock | ReentrantLock | ReentrantReadWriteLock | 自研 | |
---|---|---|---|---|---|
初級開發人員 | 使用 | 不使用 | 不使用 | 不使用 | 不使用 |
中級開發人員 | 使用 | 不使用 | 不支持 | 不使用 | 不使用 |
高級開發人員 | 使用 | 不使用 | 不支持 | 不使用 | 不使用 |
研發人員 | 使用 | 不使用 | 使用 | 使用 | 使用 |
資深研發人員 | 使用 | 使用 | 使用 | 使用 | 使用 |
synchronized | StampedLock | ReentrantLock | ReentrantReadWriteLock | 自研 | |
---|---|---|---|---|---|
業務系 | 使用 | 不使用 | 不使用 | 不使用 | 不使用 |
功能系 | 使用 | 不使用 | 使用 | 不使用 | 不使用 |
軟件系 | 使用 | 不使用 | 使用 | 使用 | 使用 |
業務模塊基本不須要關注鎖,須要鎖的地方都,高級如下程序員都使用的數據庫鎖,或者其餘庫鎖。打雜的目前在業務系沒有用過java鎖, 功能模塊的開發與維護基本是高級或者研發人員,用到鎖的地方很少,絕對大部分是使用的synchronized與ReentrantLock,在spring的代碼裏面只見到許多synchronized,ReentrantLock還沒見過 軟件繫好比netty,dubbo,大量使用ReentrantLock,一些獨立的服務好比rocketmq,核心業務重寫了ReentrantLock,還有設計本身的加鎖機制性能優化
synchronized | StampedLock | ReentrantLock | ReentrantReadWriteLock | 自研 | |
---|---|---|---|---|---|
讀多 | 不使用 | 不使用 | 不使用 | 使用 | 不使用 |
各半 | 使用 | 不使用 | 不使用 | 使用 | 不使用 |
寫多 | 使用 | 不使用 | 使用 | 不使用 | 不使用 |
全寫 | 使用 | 不使用 | 使用 | 不使用 | 不使用 |
全寫操做目前發現最多的就是計數器,計數器建議使用jdk8的LongAdders(計數器),性能超級好。注意任何計數器沒法保證絕對的精確性 ** ReentrantLock與ReentrantReadWriteLock的寫性能同樣**服務器
重入>鎖升級>自動釋放>鎖等待超時>公平>讀寫>線程中斷
在繁多,複雜的方法,代碼,邏輯之間相互調用。誰也不知道,哪一個方法,哪段代碼使用了鎖,一不當心死鎖。因此重入是最重要的一點。 除非資深研發人員不然其餘人員不該該使用StampedLock
鎖升級能夠作基本性能方面優化,就交給鎖了,可讓鎖性能在個個場景均可以保持較好的狀態,從而減小鎖開發與維護的工做量
自動釋放對初級,中級或者高級開發來講,是一個避免出現鎖問題的利器,保障開發簡單,順利。不用擔憂哪裏忘記釋放鎖,從而形成鎖問題
鎖等待超時是防止無限鎖等待而形成線程資源無限佔用與線程池無線程可用的狀況,從而讓應用沒法提供服務。是高可用服務保障的利器
複雜的環境下,不知道哪一個方法,哪一個代碼使用了讀鎖仍是寫鎖。太多未知與細節,十分頭疼,須要大量的時間與精力處理讀寫關係,得不償失。
public class SynchronizedLockMode { private static int increment = 0; private Object object = new Object( ); public synchronized void lockMethod(){ print("lockMethod"); } public synchronized static void lockStaticMethod(){ print("lockStaticMethod"); } public void lockBlock(){ synchronized( object ){ print("lockBlock"); } } }
運行代碼
@Test public void synchronizedLockModeTest(){ SynchronizedLockMode slm = new SynchronizedLockMode(); Thread thread1 = new Thread( new Runnable( ) { public void run ( ) { slm.lockMethod( ); } } ); Thread thread2 = new Thread( new Runnable( ) { public void run ( ) { SynchronizedLockMode.lockStaticMethod( ); } } ); Thread thread3 = new Thread( new Runnable( ) { public void run ( ) { slm.lockBlock( ); } } ); thread1.start( ); thread2.start( ); thread3.start( ); try { Thread.sleep( 1000 ); } catch ( InterruptedException e ) { // TODO 自動生成的 catch 塊 e.printStackTrace(); } }
運行結果
lockMethod: 0 for num0 lockBlock: 1 for num0 lockStaticMethod: 0 for num0 lockBlock: 3 for num1 lockMethod: 2 for num1 lockBlock: 5 for num2 lockStaticMethod: 4 for num1 lockBlock: 7 for num3 lockMethod: 6 for num2 lockBlock: 9 for num4 lockStaticMethod: 8 for num2 lockBlock: 11 for num5 lockMethod: 10 for num3 lockBlock: 13 for num6 lockStaticMethod: 12 for num3 lockBlock: 15 for num7 lockBlock: 17 for num8 lockMethod: 14 for num4 lockBlock: 18 for num9 lockBlock: 20 for num10 lockBlock: 21 for num11 lockStaticMethod: 16 for num4 lockBlock: 22 for num12 lockMethod: 19 for num5 lockBlock: 24 for num13 lockStaticMethod: 23 for num5 lockBlock: 26 for num14 lockMethod: 25 for num6 lockBlock: 28 for num15 lockStaticMethod: 27 for num6 lockBlock: 30 for num16 lockMethod: 29 for num7 lockBlock: 32 for num17 lockStaticMethod: 31 for num7 lockBlock: 34 for num18 lockMethod: 33 for num8 lockBlock: 36 for num19 lockStaticMethod: 35 for num8 lockStaticMethod: 38 for num9 lockStaticMethod: 39 for num10 lockStaticMethod: 40 for num11 lockStaticMethod: 41 for num12 lockStaticMethod: 42 for num13 lockMethod: 37 for num9 lockStaticMethod: 43 for num14 lockMethod: 44 for num10 lockStaticMethod: 45 for num15 lockMethod: 46 for num11 lockStaticMethod: 47 for num16 lockMethod: 48 for num12 lockStaticMethod: 49 for num17 lockMethod: 50 for num13 lockStaticMethod: 51 for num18 lockMethod: 52 for num14 lockStaticMethod: 53 for num19 lockMethod: 54 for num15 lockMethod: 55 for num16 lockMethod: 56 for num17 lockMethod: 57 for num18 lockMethod: 58 for num19
從上面的執行能夠是否發現一個問題,答應是亂序的,自增數據是亂序的。不少人認爲:絕對是java設計的失誤....... //???使用一個圖片
來邏輯推理下:
這是咱們討論的論題,也是一個不當心容易犯錯的問題。
演示代碼,有四個方法。
public synchronized void lockThisObject(){ sleep("synchronized method"); } public void VerificationLockMethodIsWhatObject(){ synchronized( this ){ sleep("synchronized block lock this" , false); } } public synchronized static void lockClassObject(){ sleep("synchronized method static "); } public void VerificationLockStaticMethodIsWhatObject(){ synchronized( SynchronizedLockMode.class ){ sleep("synchronized block lock SynchronizedLockMode.class" , false); } } private static void sleep(String lock , boolean boo){ if(boo){ sleep( lock ); }else{ System.out.println( lock + " execute" ) ; } } private static void sleep(String lock){ sleep0( lock ); } private static void sleep0(String lock){ try { System.out.println( lock + " start sleep" ) ; Thread.sleep( 10000 ); System.out.println( lock + " end sleep" ) ; } catch ( InterruptedException e ) { // TODO 自動生成的 catch 塊 e.printStackTrace(); } }
代碼解讀 有四個方法分別是靜態方法,非靜態方法,兩個方法裏面有synchronized block。 四個方法分別組合,測試方法的互斥行。輸出內容是按照調用方法的循序執行的,synchronized block方法的輸出結果在synchronized 方法以後,那麼表示兩個方法是互斥的 組合: 1. 鎖靜態方法 塊鎖鎖住this 2. 鎖靜態方法 塊鎖鎖住Class 3. 鎖非靜態方法 塊鎖鎖住this 4. 鎖非靜態方法 塊鎖鎖住Class
test代碼與結果 第一個組合
SynchronizedLockMode slm = new SynchronizedLockMode(); Thread thread1 = new Thread( new Runnable( ) { public void run ( ) { SynchronizedLockMode.lockClassObject( ); } } ); Thread thread2 = new Thread( new Runnable( ) { public void run ( ) { slm.verificationLockMethodIsWhatObject( ); } } ); System.out.println( "第一個組合 鎖靜態方法 塊鎖鎖住this" ) ; thread1.start( ); sleep();//暫停1秒,是爲了線程執行不亂 thread2.start( ); sleep(10000);// 暫停10秒,是由於鎖方法會展廳5秒,同時爲輸出內容不會與上面組合亂 System.out.println( "第一個組合 執行結束" ) ; 第一個組合 鎖靜態方法 塊鎖鎖住this synchronized method static start sleep synchronized block lock this execute synchronized method static end sleep 第一個組合 執行結束
第二個組合
thread1 = new Thread( new Runnable( ) { public void run ( ) { SynchronizedLockMode.lockClassObject( ); } } ); thread2 = new Thread( new Runnable( ) { public void run ( ) { slm.verificationLockStaticMethodIsWhatObject( ); } } ); System.out.println( "第二個組合 鎖靜態方法 塊鎖鎖住Class" ) ; thread1.start( ); sleep(); thread2.start( ); sleep(10000); System.out.println( "第二個組合 執行結束" ) ; 第二個組合 鎖靜態方法 塊鎖鎖住Class synchronized method static start sleep synchronized method static end sleep synchronized block lock SynchronizedLockMode.class execute 第二個組合 執行結束
第三個組合
thread1 = new Thread( new Runnable( ) { public void run ( ) { slm.lockThisObject( ); } } ); thread2 = new Thread( new Runnable( ) { public void run ( ) { slm.verificationLockMethodIsWhatObject( ); } } ); System.out.println( "第三個組合 鎖非靜態方法 塊鎖鎖住this" ) ; thread1.start( ); sleep(); thread2.start( ); sleep(10000); System.out.println( "第三個組合 執行結束" ) ; 第三個組合 鎖非靜態方法 塊鎖鎖住this synchronized method start sleep synchronized method end sleep synchronized block lock this execute 第三個組合 執行結束
第四個組合
thread1 = new Thread( new Runnable( ) { public void run ( ) { slm.lockThisObject( ); } } ); thread2 = new Thread( new Runnable( ) { public void run ( ) { slm.verificationLockStaticMethodIsWhatObject( ); } } ); System.out.println( "第四個組合 非鎖靜態方法 塊鎖鎖住Class" ) ; thread1.start( ); sleep(); thread2.start( ); sleep(10000); System.out.println( "第四個組合 執行結束" ) ; 第四個組合 非鎖靜態方法 塊鎖鎖住Class synchronized method start sleep synchronized block lock SynchronizedLockMode.class execute synchronized method end sleep 第四個組合 執行結束
經過結果分析會發現第二,三組合的輸出結果是有序的。麻煩在看看二,三組合調用的方法 第二個組合是: 鎖靜態方法 塊鎖鎖住Class 第三個組合是: 鎖非靜態方法 塊鎖鎖住this
public synchronized static void XXXXX(){ // 鎖的對象是 當前的 class }
public synchronized void XXXXX(){ // 鎖的對象是 當前的 this }
這是阿里大神fashjson與driud的做者溫 對synchronized簡單總結
某日,對某個優化過的系統進行,上線前的性能壓測,壓測結果大大的出乎人意料,優化以後比優化以前的TPS只多200+。在16cpu的服務器不該該出現這樣的狀況。
是否是接口中數據庫操做的問題,MySQL通用日誌裏記錄的sql基本一致,慢日誌裏面沒有記錄接口操做的sql。是否是測試人員的測試數據十分重複,更新操做形成鎖超時,準備排除鎖超時狀況,測試人員與業務開發人員反饋,查詢接口也同樣,數據狀態良好 是否是代碼問題,分析最後的此時結果,發現全部壓測接口都這樣。包括簡單條主鍵查詢的SQL Why? 奇蹟了,數據庫與應用一切正常啊。被逼無賴,在每一個核心地方輸出調用時間,也沒問題。發現全部的接口的使用了RateLimiter的acquire方法,深刻一看,有synchronized。每次接口的調用都會進入下面的代碼,而每次都會有鎖爭奪。
google guava 的RateLimiter 限流的核心計算代碼使用的synchronized,google大神都證實了synchronized的優秀
某個深夜,老胡在看ReentrantReadWriteLock源碼,想用ReentrantReadWriteLock代替ReentrantLock提升性能,反覆的看調用流程與實現細節(看了兩個多小時),腦海慢慢呈現整個調用流程與實現細節的流程與邏輯圖,發現不對勁啊,可能一不當心出現死鎖
public void reentrantReadWriteLock() throws InterruptedException{ ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock(); ReadLock readLock = rrwl.readLock( ); WriteLock wrtieLock = rrwl.writeLock( ); readLock.lock( ); readLock.lock( ); readLock.unlock( ); readLock.unlock( ); wrtieLock.lock( ); wrtieLock.lock( ); wrtieLock.unlock( ); wrtieLock.unlock( ); wrtieLock.lock( ); readLock.lock( ); wrtieLock.unlock( ); readLock.unlock( ); readLock.lock( ); //wrtieLock.lock( ); wrtieLock.lockInterruptibly( ); readLock.unlock( ); wrtieLock.unlock( ); }
通過測試以後,老胡出了一點冷汗,這個死鎖隱藏得太深了。還好是老胡慢慢,慢,看出來了,以老胡的編碼方式,還真得出現這樣的死鎖
在發現死鎖現象同一個深夜,老胡在仔細反覆的看公佈與不公平,讀寫鎖的細節。反覆的看調用流程與實現細節,一邊準備與周公喝茶了,一個低頭砸到桌子上,腦海裏整個調用流程與實現細節的流程與邏輯圖砸出一個閃光,找到一個問題 在高併發下,不少線程爭奪一個鎖的時候,在隊列的裏面的鎖可能能難爭奪到鎖,爭奪不到,會餓死啊。
鎖方式 | ReentrantLock | ReentrantReadWriteLock |
---|---|---|
lock | 飢餓 | 飢餓 |
lockInterruptibly | 飢餓 | 飢餓 |
tryLock | 不飢餓 | 不飢餓 |
tryLock(超時) | 超時餓醒 | 超時餓醒 |
在高併發狀況下,使用tryLock(超時)杜絕 飢餓。沒得到鎖,能夠直接異常與返回異常結果
private Object object = new Object(); public void a(){ synchronized(object){ } } public void b(){ synchronized(object){ } } // c 方法與a,b方法的鎖不是一個,在這個類裏面有兩個鎖分別是 object與this, public void c(){ synchronized(this){ } }
// 同時存在 a b鎖,很容易不當心死鎖。 ReentrantLock a= new ReentrantLock(); ReentrantLock b= new ReentrantLock();
至此 2018年01月25日00.26. 歷經一個多月,才寫完。也真的佩服文字表達能力與技術描述能力,1個多月啊。
歡迎大神蝌蚪,轉載。大神蝌蚪的網址是www.kedou.me