1 synchronized
1.1 synchronized介紹
- synchronized機制提供了對每個對象相關的隱式監視器鎖,並強制所有鎖的獲取和釋放都必須在同一個塊結構中。當獲取了多個鎖時,必須以相反的順序釋放。即synchronized對於鎖的釋放是隱式的。
- synchronized同步塊對於同一條線程是可重入的,不會出現把自己鎖死的問題。
- synchronized可以修飾類、方法(包括靜態方法)、代碼塊。修飾類和靜態方法時,鎖的對象是Class對象;修飾普通方法時,鎖的是調用該方法的對象;修飾代碼塊時,鎖的是方法塊括號里的對象。
- synchronized性能中避免“讀/讀”操作,但讀操作頻繁,通過ReentrantLock提供的ReadWriteLock讀寫鎖來解決該問題;阻塞線程時,需要OS不斷的從用戶態轉到核心態,消耗處理器時間,通過自適應自旋來解決該問題。
- synchronized的鎖是存放在Java對象頭里的。
1.2 synchronized的三種使用場景
1.2.1 修飾實例方法
1)說明:對當前對象實例this加鎖
2)示例:
public class Demo1 {
public synchronized void methodA() {
System.out.println("synchronized 修飾 普通實例方法");
}
}
1.2.2 修飾靜態方法
1)對當前類的Class對象加鎖
2)示例
public class Demo2 {
public synchronized static void methodB() {
System.out.println("synchronized 修飾 靜態方法");
}
}
1.2.3 修飾代碼塊
1)給指定對象加鎖
2)說明
public class Demo3 {
public void methodC() {
synchronized(this) {
System.out.println("synchronized代碼塊對當前對象加鎖");
}
}
public void methodC() {
synchronized(Demo3.Class) {
System.out.println("synchronized代碼塊對當前類的Class對象加鎖");
}
}
}
1.3 synchronized實現同步的基礎
Java中每一個對象都可以作為鎖,這是synchronized實現同步的基礎:
- 普通同步方法:鎖是當前實例對象;
- 靜態同步方法:鎖是當前類的Class對象;
- 同步方法塊:鎖是括號里面的對象;
同步代碼塊:monitorenter指令插入到同步代碼塊的開始位置,monitorexit指令插入到同步代碼塊的結束位置,JVM需要保證每一個monitorexit和monitorenter相對應,任何對象都有一個monitor與之相對應,任何對象都有一個monitor與之相關聯,當且一個monitor被持有之后,他處於鎖定狀態,線程執行到monitorenter指令時,將會嘗試獲取對象所對應的monitor所有權,即嘗試獲取對象的鎖。
同步方法:synchronized方法則會被翻譯成普通的方法調用和返回指令如:invokevirtual、areturn指令,在VM字節碼層面並沒有任何特別的指令來實現被synchronized修飾的方法,而是在Class文件的方法表中將該方法的access_flags字段中的synchronized標志位置1,表示該方法是同步方法並使用調用該方法的對象或該方法所屬的Class在JVM的內部對象表示Klass做為鎖對象。
1.4 synchronized反編譯字節碼
synchronized字節碼層面實現的鎖(鎖計數器)。
- synchronized關鍵字經編譯后,在同步塊前后分別形成monitorenter和monitorexit兩個字節碼指令;
- 在執行monitorenter指令時,首先要嘗試獲取對象的鎖,若這個對象沒被鎖定,或當前線程已經擁有了那個對象的鎖,就把鎖的計數器加1;
- 在執行monitorexit指令時會將鎖計數器減1,當計數器為0時,鎖就會被釋放;
- 若獲取對象鎖失敗,當前線程進入阻塞等待,直到對象鎖被另外一個線程釋放為止。
1.4.1 monitorenter
(重入鎖的體現)每個對象都有一個監視器鎖(monitor),當monitor被占用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權。
- 如果monitor的進入數為0,則該線程進入monitor,然后將進入數設置為1,該線程即為monitor的所有者。
- 如果線程已經占有該monitor,只是重新進入,則進入monitor的進入數加1。
- 如果其他線程已經占用了monitor,則該線程進入阻塞狀態,直至monitor的進入數為0,再重新嘗試獲取monitor的所有權。
1.4.2 monitorexit
- 執行monitorexit的線程必須是object所對應的monitor的所有者。
- 指令執行時,monitor的進入數減1,如果減1后進入數為0,那么線程就會退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個monitor的所有權。
1.5 Java對象
1.5.1 Java對象組成
Java對象存儲在堆內存中,對象由對象頭、實例變量和填充字節組成。
1.5.2 對象頭
- synchronized用的鎖是存放在Java對象頭里。Hotspot虛擬機的對象頭主要包括兩部分數據:Mark Word(標記字段)、Klass Pointer(類型指針)。
- Klass Point是對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。
- Mark Word用於存儲對象自身的運行時數據,它是實現輕量級鎖和偏向鎖的關鍵。如哈希碼(HashCode)、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程 ID、偏向時間戳等等。Java對象頭一般占有兩個機器碼(在32位虛擬機中,1個機器碼等於4字節,也就是32bit),但是如果對象是數組類型,則需要三個機器碼,因為JVM虛擬機可以通過Java對象的元數據信息確定Java對象的大小,但是無法從數組的元數據來確認數組的大小,所以用一塊來記錄數組長度。
下圖是Java對象頭的存儲結構(32位虛擬機):
對象的hashCode | 對象的分代年齡 | 是否為偏向鎖 | 鎖標志位 |
---|---|---|---|
25bit | 4bit | 1bit | 2bit |
對象頭信息是與對象自身定義的數據無關的額外存儲成本,單考慮VM的空間小了,Mark Word設計成一個非固定的數據結構以便在極小的空間內存存儲盡量多的數據,會根據對象的狀態復用自己的存儲空間,即Mark Word是隨着程序的運行發生變化。
1.5.3 實例數據
是對象存儲的真正有效信息,存儲着自身定義的和從父類繼承下來的實例字段,字段的存儲順序會受到虛擬機的分配策略和字段在Java源碼中定義順序的影響,這部分內存按4字節對齊。
1.5.4 對齊填充字段
由於虛擬機要求對象起始地址必須是8字節的整數倍。填充數據不是必須存在的,僅僅是為了字節對齊。
2 ReentrantLock
2.1 ReentrantLock介紹
- Lock提供一種無條件的、可輪詢的、定時的以及可中斷的鎖獲取操作,所有加鎖和解鎖都是顯式的。
- ReentrantLock實現Lock接口,並提供與synchronized相同的互斥性和內存可見性。
- ReentrantLock提供和synchronized一樣的可重入的加鎖語義。
- ReentrantLock是顯式鎖,需要顯式進行lock以及unlock操作。形式比內置鎖復雜,必須在finally塊中釋放鎖,否則如果在被保護的代碼中拋出異常,這個鎖就永遠都無法釋放。加鎖時,需要考慮在try塊中拋出異常的情況,如果可能使對象處於某種不一致的狀態,則需要更多的try-catch或try-finally代碼塊。
2.2 ReentrantLock示例
Lock lock = new ReentrantLock();
...
lock.lock();
try{
//更新對象狀態
//捕獲異常,並在必要時恢復不變性條件
}finally{
lock.unlock();
}
2.3 使用ReentrantLock的靈活性
(也是與synchronized的區別)
2.3.1 等待可中斷
使用lock.lockInterruptibly()
可以使得線程在等待鎖支持響應中斷;
使用lock.tryLock()
可以使線程在等待一段時間過后如果還未獲得鎖就停止等待而非一直等待,更好的避免飢餓和死鎖問題;
2.3.2 公平鎖
默認情況下是非公平鎖,但是也可以是公平鎖,公平鎖就是鎖的等待隊列的FIFO,不建議使用,會浪費許多時鍾周期,達不到最大利用率。
2.3.3 鎖可綁定多個條件
與ReentrantLock搭配的通信方式是Condition
,且可以為多個線程建立不同的Codition。
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
condition.await(); //等價於synchronized中的wait()方法
condition.signal(); //等價於notify()
condition.signalAll(); //等價於notifyAll()
2.4 讀-寫鎖
2.4.1 讀寫鎖的出現原因
ReentrantLock實現一種標准的互斥鎖,每次最多只有一個線程能持有ReentrantLock,限制了並發性,互斥是一種保守的加鎖策略,雖然避免了“寫/寫”沖突和“寫/讀”沖突,但也避免了“讀/讀”沖突,而大部分情況下讀操作比較多,如果此時能夠放寬加鎖需求,允許多個讀操作的線程同時訪問數據結構,可以提升程序的性能(只要每個線程保證讀取到最新的數據,並且在讀取數據時不會有其他線程修改數據就行)
2.4.2 ReentrantLock提供的非互斥的讀寫鎖的定義
一個資源可以被多個讀操作訪問,或者被一個寫操作訪問,但兩者不能讀寫操作同時進行。(多讀、一寫、不可同讀寫)
讀-寫鎖是一種性能優化措施,可以實現更高的並發性,提高程序的性能。
當鎖的持有時間較長並且大部分操作都不會修改被守護的資源時,讀-寫鎖可以提高並發性。
3 Lock接口
3.1 Lock接口介紹
Lock接口提供一種無條件的、可輪詢的、定時的以及可中斷的鎖獲取操作,所有加鎖和解鎖都是顯示的。
3.2 Lock方法
void lock();
:獲取鎖。void lockInterruptibly() throws InterruptedException;
:若當前線程未被中斷,獲取鎖。boolean tryLock();
:僅當調用時鎖為空閑狀態才獲取鎖。boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
:如果鎖在給定的等待時間內空閑,並且當前線程未被中斷,則獲取鎖。void unlock();
:釋放鎖。Condition newCondition();
:返回綁定在此Lock實例的新Condition實例。
4 鎖優化
4.1 jdk1.6對鎖進行優化:
自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖。
4.2 鎖的四個狀態:
無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態。
鎖可以升級(隨着鎖的競爭激烈程度而升級),但是不可降級。
4.3 自旋鎖
- 引入原因:線程的阻塞和喚醒需要CPU從用戶態轉為核心態以及核心態轉為用戶態,頻繁的阻塞和喚醒對CPU來說是一件負擔很重的工作,勢必會給系統的並發性能帶來很大的壓力。同時我們發現在許多應用上面,對象鎖的鎖狀態只會持續很短一段時間,為了這一段很短的時間頻繁地阻塞和喚醒線程是非常不值得的。
- 原理:就是讓該線程等待一段時間(執行一段無意義的循環即可(自旋)),不會被立即掛起,看持有鎖的線程是否會很快釋放鎖。(線程自循環等待,不立即掛起)
- 缺點:自旋不能代替阻塞,可以避免線程切換帶來的開銷,但是會占用處理器的時間,若持有鎖的線程很快釋放鎖,則自旋效率好;反之,自旋的線程會白白消耗掉處理器的資源,帶來性能上的浪費。
- 針對缺點的解決方案:自旋等待時間有限度——超過定義的時間沒有獲得鎖就應該被掛起。自旋鎖是JDK1.4.2引入,默認關閉,使用-XX:UseSpining開啟,在JDK1.6默認開啟,默認自旋次數為10次,可以通過-XX:PreBlockSpin調整。最終是通過自適應自旋鎖來解決這一問題。
4.4 自適應自旋鎖
- 引入原因:自旋鎖的自旋次數是固定的,當持有鎖不能很快釋放鎖時,效率低下,浪費處理器資源,自旋次數需要通過-XX:PreBlockSpin調整。所以引入自適應自旋鎖不再固定自旋次數。
- 原理:由前一次在同一個鎖上的自旋時間及鎖的擁有者狀態決定。線程如果自旋成功了,那么下次自旋的次數會更加多,因為虛擬機認為既然上次成功了,那么此次自旋也很有可能會再次成功,那么它就會允許自旋等待持續的次數更多。反之,如果對於某個鎖,很少有自旋能夠成功的,那么在以后要或者這個鎖的時候自旋的次數會減少甚至省略掉自旋過程,以免浪費處理器資源。(主要就是看前一次自旋成功與否,成功多就增加自旋次數;成功少,就減少自旋次數)
4.5 鎖消除
- 引入原因:在某些情況下,JVM檢測到不可能存在共享數據競爭,如果繼續加鎖,降低性能,無意義。
- 原理:鎖消除依據是逃逸分析的數據支持,檢測到不存在共享數據競爭,就消除鎖,從而節省請求鎖的時間。(逃逸分析檢測是否有共享數據競爭,若有,消除鎖)
4.6 鎖粗化
- 引入原因:一系列的連續加鎖解鎖操作,導致不必要的性能損耗。
- 原理:將多個連續的加鎖、解鎖操作連接在一起,擴展成一個范圍更大的鎖。如Vector的add操作操作,JVM檢測到對同一個對象(vector)連續加鎖、解鎖操作,則合並成一個更大范圍的加鎖、解鎖操作,進行鎖粗化。(多個連續加鎖、解鎖合並成一個更大范圍的鎖)
4.7 輕量級鎖(00)
- 引入原因:在沒有多線程競爭的前提下,減少傳統的重量級鎖使用操作系統互斥量產生的性能損耗。
- 原理:當關閉偏向鎖功能或者多個線程競爭偏向鎖導致偏向鎖升級為輕量級鎖,則會嘗試獲取輕量級鎖,其步驟如下:
獲取鎖
- 1)判斷當前對象是否處於無鎖狀態(hashcode、0、01),若是,則JVM首先將在當前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的Mark Word的拷貝(官方把這份拷貝加了一個Displaced前綴,即Displaced Mark Word);否則執行步驟 3);
- 2)JVM利用CAS操作嘗試將對象的Mark Word更新為指向Lock Record的指針,如果成功表示競爭到鎖,則將鎖標志位變成00(表示此對象處於輕量級鎖狀態),執行同步操作;如果失敗則執行步驟 3);
- 3)判斷當前對象的Mark Word是否指向當前線程的棧幀,如果是則表示當前線程已經持有當前對象的鎖,則直接執行同步代碼塊;否則只能說明該鎖對象已經被其他線程搶占了,這時輕量級鎖需要膨脹為重量級鎖,鎖標志位變成10,后面等待的線程將會進入阻塞狀態;
釋放鎖:輕量級鎖的釋放也是通過CAS操作來進行的,主要步驟如下:
- 1)取出在獲取輕量級鎖保存在Displaced Mark Word中的數據;
- 2)用CAS操作將取出的數據替換當前對象的Mark Word中,如果成功,則說明釋放鎖成功,否則執行 3);
- 3)如果CAS操作替換失敗,說明有其他線程嘗試獲取該鎖,則需要在釋放鎖的同時需要喚醒被掛起的線程。
- 性能分析:對於輕量級鎖,其性能提升的依據是“對於絕大部分的鎖,在整個生命周期內都是不會存在競爭的”,如果打破這個依據則除了互斥的開銷外,還有額外的CAS操作,因此在有多線程競爭的情況下,輕量級鎖比重量級鎖更慢;
4.8 偏向鎖(01)
- 引入原因:為了在無多線程競爭的情況下盡量減少不必要的輕量級鎖執行路徑。輕量級鎖的加鎖解鎖操作是需要依賴多次CAS原子指令的。
- 原理
獲取鎖:
- 1)檢測Mark Word是否為可偏向狀態,即是否為偏向鎖1,鎖標識位為01;
- 2)若為可偏向狀態,則測試線程ID是否為當前線程ID,如果是,則執行步驟 5),否則執行步驟 3);
- 3)如果線程ID不為當前線程ID,則通過CAS操作競爭鎖,競爭成功,則將Mark Word的線程ID替換為當前線程ID,否則執行步驟 4);
- 4)通過CAS競爭鎖失敗,證明當前存在多線程競爭情況,當到達全局安全點,獲得偏向鎖的線程被掛起,偏向鎖升級為輕量級鎖,然后被阻塞在安全點的線程繼續往下執行同步代碼塊;
- 5)執行同步代碼塊。
釋放鎖:偏向鎖的釋放采用了一種只有競爭才會釋放鎖的機制,線程是不會主動去釋放偏向鎖,需要等待其他線程來競爭。偏向鎖的撤銷需要等待全局安全點(這個時間點是上沒有正在執行的代碼)。其步驟如下:
- 1)暫停擁有偏向鎖的線程,判斷鎖對象是否還處於被鎖定狀態;
- 2)撤銷偏向鎖,恢復到無鎖狀態(01)或者輕量級鎖的狀態;
4.9 重量級鎖(10)
重量級鎖通過對象內部的監視器(monitor)實現,其中monitor的本質是依賴於底層操作系統的Mutex Lock實現,操作系統實現線程之間的切換需要從用戶態到內核態的切換,切換成本非常高。
4.10 鎖的優缺點的對比
鎖 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
偏向鎖 | 加解鎖不需要額外操作,速度較快 若是發生線程間的鎖競爭,會產生鎖撤銷的消耗 | 適用於只有一個線程訪問同步塊的場景 | |
輕量級鎖 | 競爭的線程不會阻塞,程序響應速度塊 | 不斷的自旋會消耗CPU資源 | 追求響應時間,同步塊執行速度很快 |
重量級鎖 | 線程競爭不使用自旋,CPU消耗少 | 線程會阻塞,相應慢 | 追求吞吐量,同步塊執行時間較長 |
Q&A
synchronized方法和synchronized塊的區別
- synchronized塊:是一種細粒度的並發控制,只會將塊中的代碼同步,位於方法內、synchronized塊之外的代碼是可以被多個線程同時訪問到的,鎖的是方法塊后面括號里的對象;synchronized方法是一種粗粒度的並發控制,某一時刻,只能有一個線程執行該synchronized方法,鎖的是調用該方法的對象。
- 同步代碼塊:monitorenter指令插入到同步代碼塊的開始位置,monitorexit指令插入到同步代碼塊的結束位置,JVM需要保證每一個monitorexit和monitorenter相對應,任何對象都有一個monitor與之相對應,任何對象都有一個monitor與之相關聯,當且一個monitor被持有之后,他處於鎖定狀態,線程執行到monitorenter指令時,將會嘗試獲取對象所對應的monitor所有權,即嘗試獲取對象的鎖;同步方法:synchronized方法則會被翻譯成普通的方法調用和返回指令如:invokevirtual、areturn指令,在VM字節碼層面並沒有任何特別的指令來實現被synchronized修飾的方法,而是在Class文件的方法表中將該方法的access_flags字段中的synchronized標志位置1,表示該方法是同步方法並使用調用該方法的對象或該方法所屬的Class在JVM的內部對象表示Klass做為鎖對象。在常量池中添加了ACC_ SYNCHRONIZED標識符,JVM就是根據該標識符來實現方法的同步。
synchronized和ReentrantLock的異同
同:
- 都是可重入的;
- 都屬於同步互斥的手段;
異:
1 底層:synchronized是原生語法層面的互斥鎖;ReentrantLock是API層面的互斥鎖;
2. 加鎖釋放鎖范式:synchronized是內置鎖,獲取多個鎖后,以相反的順序隱式釋放鎖;ReentrantLock必須顯式加鎖釋放鎖,且可以自由的順序釋放鎖。
3. 功能:ReentrantLock增加高級功能:等待可中斷、可實現公平鎖、鎖可以綁定多個條件。
功能 | 說明 |
---|---|
等待可中斷 | 當持有鎖的線程長期不釋放鎖的時候,正在等待的線程可以選擇放棄等待,改為處理其他事情,可中斷特性適用於處理執行時間非常長的同步塊。 |
公平鎖 | ReentrantLock可以是公平鎖,默認為非公平鎖,通過帶布爾值的構造函數使用公平鎖,公平鎖是多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來一次獲得鎖;synchronized是非公平鎖,非公平鎖是不保證的,在鎖釋放時,任何一個等待鎖的線程都有機會獲得鎖。 |
鎖綁定多個條件 | 指一個ReentrantLock對象可同時綁定多個Condition對象,多次調用newCondition()方法;而在synchronized中,鎖對象的wait()和notify()或notifyAll()方法可以實現一個隱含的條件,若要和多個條件關聯,需要額外的添加一個鎖。 |
什么是可重入鎖?
重入鎖實現重入性:每個鎖關聯一個線程持有者和計數器:
- 當計數器為0時表示該鎖沒有被任何線程持有,那么任何線程都可能獲得該鎖而調用相應的方法;
- 當某一線程請求成功后,JVM會記下鎖的持有線程,並且將計數器置為1;
- 此時其它線程請求該鎖,則必須等待;而該持有鎖的線程如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增+1;
- 當線程退出同步代碼塊時,計數器會遞減-1,如果計數器為0,則釋放該鎖。
synchronized鎖的存儲位置?
- synchronized鎖存放的位置是Java對象頭里,如果對象是數組類型,則JVM用3個字節寬存儲對象頭,如果對象是非數組類型,則用2字節寬度存儲對象頭。
- 對象頭由mark word+類型指針組成。mark word默認存儲對象的hashcode、分代年齡和鎖標志位。
synchronized鎖存儲在對象頭,何為對象頭?
對象頭中包括兩部分數據:標記字段和類型指針;
- 類型指針Klass Point是對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例;
- 標記字段Mark Word是一個非固定的數據結構,用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程 ID、偏向時間戳等等。
Lock與synchronized的主要區別:
- 類型:Lock是一個接口;synchronized是Java中的關鍵字,synchronized是內置的語言實現;
- 異常:Lock在發生異常時,如果沒有主動通過unLock釋放鎖,可能造成死鎖現象,使用Lock時需要在finally塊中釋放鎖;synchronized在發生異常時,會自動釋放線程中的占有的鎖,不會導致死鎖現象發生。
- 加鎖釋放鎖:synchronized機制提供了對與每個對象相關的隱式的監視器鎖,並強制所有鎖的獲取和釋放都必須在同一個塊結構中。當獲取了多個鎖時,他們必須以相反的順序釋放。即synchronized對於鎖的釋放是隱式的。而Lock機制必須顯式的調用Lock對象的unlock()方法,這樣,獲取鎖和釋放鎖可以不在同一個塊中,這樣可以以更自由的順序釋放鎖。
- 響應中斷:Lock可以讓等待鎖的線程響應中斷;synchronized不可以讓等待鎖的線程響應中斷,等待的線程會一直等待下去,不能夠響應中斷;
- 獲取鎖成功與否:通過Lock可以知道有沒有成功獲取鎖;synchronized不能;
- 讀操作:Lock可以提高多個線程進行讀操作的效率(通過ReadWriteLock);而synchronized避免讀/讀操作
當一個線程進入一個對象的一個synchronized()方法后,其他線程能進入此對象的什么樣的方法?
- 當一個線程在調用synchronized()方法的過程中,另一個線程可以訪問同一個對象的非synchronized()方法;
- 當一個線程在調用synchronized()方法的過程中,另一個線程可以訪問靜態synchronized()方法,因為靜態方法的同步鎖是當前類的字節碼,與非靜態方法不能同步;
- 當一個線程在調用synchronized()方法的過程中,在這個方法內部調用了wait()方法,則另一個線程就可以訪問同一個對象的其他synchronized()方法。
什么是同步代碼塊和內置鎖?
同步代碼塊
- 同步代碼塊包括兩部分:一個作為鎖定的對象引用,一個作為由這個鎖保護的代碼塊。
- 靜態的synchronized方法以Class對象作為鎖;
內置鎖
- 每個Java對象都可以用於做一個實現同步的鎖,則這些鎖稱為內置鎖或監視器鎖。
- 獲得內置鎖的唯一途徑是進入由這個鎖保護的同步代碼塊或方法。
- 內置鎖:互斥且可重入。
為什么要創建一種與內置鎖相似的新加鎖機制?
內置鎖能很好的工作,但是在功能上存在一些局限性,如synchronized無法中斷一個正在等待獲取鎖的線程,或者無法在請求獲取一個鎖的時候無限地等待下去,內置鎖必須在獲取該鎖的代碼塊中釋放,簡化了編碼工作,且與異常處理操作實現很好的交互,但無法實現非阻塞結構的加鎖機制。所以需要提供一種更靈活的加鎖機制來提供更好的活躍性或性能。
synchronized和ReentrantLock的抉擇
- 高級功能:當需要可定時的、可輪詢的、可中斷鎖、公平鎖以及非塊結構的鎖(鎖分段技術)時,才使用ReentrantLock,否則優先使用synchronized,畢竟現在JVM內置的是synchronized。
- ReentrantLock的危險性:如果在try-finally中,finally未進行unlock,就會導致鎖沒有釋放,無法追蹤最初發生錯誤的位置。
ReentrantLock中斷和非中斷加鎖區別?
ReentrantLock的中斷和非中斷加鎖模式的區別在於:線程嘗試獲取鎖操作失敗后,在等待過程中,如果該線程被其他線程中斷了,它是如何響應中斷請求的。lock()
方法會忽略中斷請求,繼續獲取鎖直到成功;而lockInterruptibly()
則直接拋出中斷異常來立即響應中斷,由上層調用者處理中斷。
參考書籍
《Java並發編程的藝術》
《Java並發編程實戰》
《深入理解Java虛擬機》