Java多線程并發(fā)之ReentrantLock
ReentrantLock
公平鎖和非公平鎖
這個類是接口 Lock的實現(xiàn)類,也是悲觀鎖的一種,但是它提供了 lock和 unlock方法用于主動進行鎖的加和拆。在之前使用的 sychronized關鍵字是隱式加鎖機制,而它是顯示加鎖,同時,這個類的構造方法提供了公平和非公平的兩種機制。
什么是公平和非公平呢?就是多線程對共享資源進行爭奪的時候,會出現(xiàn)一個線程或幾個線程完全占有共享資源,使得某些線程在長時間處于等待狀態(tài)。公平就是要等待時間過長的線程先獲得鎖。
而在 ReentrantLock類中,提供了公平鎖和非公平鎖的使用。
在
ReentrantLock源碼中,構造器提供了一個參數(shù)入口,
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}當fair為true的時候,會創(chuàng)造一個
FairSync對象給sync屬性,FairSync是繼承自Sync的類,其中有一個Lock方法,而在ReentrantLock的Lcok中使用的是sync屬性的Lock方法,故能夠保證“公平”。使用非公平鎖就不需要在構造器中傳參數(shù)。
在使用的時候,需要手動上鎖和解鎖。
使用公平鎖,會將占優(yōu)勢的線程進行限制,恢復掛起的線程,但是這個過程在CPU層面來講,是存在明顯時間差異的,非公平鎖的執(zhí)行效率相對更高,所以一般來說不建議使用公平鎖,除非現(xiàn)實業(yè)務上需要符合實際需求。
重入鎖
ReentrantLock本身還支持重入的功能。
重入鎖(Reentrant Lock)是一種支持重入的獨占鎖,它允許線程多次獲取同一個鎖,在釋放鎖之前必須相應地多次釋放鎖。重入鎖通常由兩個操作組成:上鎖(lock)和解鎖(unlock)。當一個線程獲取了重入鎖后,可以再次獲取該鎖而不被阻塞,同時必須通過相同數(shù)量的解鎖操作來釋放鎖。
重入鎖具有如下特點:
- 重入性:重入鎖允許同一個線程多次獲取同一把鎖,避免了死鎖的發(fā)生。
- 獨占性:與公平鎖和非公平鎖一樣,重入鎖也是一種獨占鎖,同一時刻只能有一個線程持有該鎖。
- 可中斷性:重入鎖支持在等待鎖的過程中中斷該線程的執(zhí)行。
- 條件變量:在使用 java.util.concurrent.locks.Condition 類配合重入鎖實現(xiàn)等待/通知機制時,等待狀態(tài)總是與重入鎖相關聯(lián)的。
重入鎖相對于 synchronized 關鍵字的優(yōu)勢在于,重入鎖具有更高的靈活性和擴展性,支持公平鎖和非公平鎖、可中斷鎖和可輪詢鎖等特性,能夠更好地滿足多線程環(huán)境下的并發(fā)控制需要。synchroized也有重入性。
ReentrantLock lock = new ReentrantLock(true);
public void get(){
while(true){
try{
lock.lock();
lock.lock();
}catch(Exception exception){
}finally{
lock.unlock();
lock.unlock();
}
}
}可重入的前提 lock是同一個對象,而關鍵字 synchroized的 Monitor也是同一個對象充當,才能判定為重入。
public void get(){
while(true){
synchronized(this){
System.out.println("外層");
synchronized(this){
System.out.println("內層");
}
}
}
}那么Java是怎么檢測鎖的重入和獲取鎖的次數(shù)的呢?在之前說過的 ObjectMobitor的C++源代碼中有 _recursions和_count來記錄鎖的重入次數(shù)和線程獲取鎖的次數(shù)。這樣在Java層面就表示一個鎖對象都擁有一個鎖計數(shù)器 _count和一個指向持有這個鎖的線程的指針 _owner,只有當前持有鎖的線程才能使得計數(shù)器+1,其他線程只有等待鎖被釋放(計數(shù)器置0)才能持有并+1。
在源碼中,非公平鎖的lock方法如下:
//ReentrantLock類中:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//0的參數(shù)為是expect,是期望值,而1是update,是更新值在執(zhí)行comparaAndSetState方法的時候,它會詢問鎖的計數(shù)器(在底層執(zhí)行compareAndSwapInt的本地方法),并期望數(shù)值為0,如果為0返回true,然后設置執(zhí)行線程主是當前線程。如果非0,那么他就會執(zhí)行acquire:
//AbstractQueuedSynchronizer類中:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//這里的tryAcquire,需要在其繼承的子類中進一步實現(xiàn)對應的功能
//子類可以根據(jù)自己的需要重新定義tryAcquire(int arg)的實現(xiàn)方式,從而實現(xiàn)更優(yōu)秀的鎖控制方案:
//而在其子類FairSync中便覆蓋了這個方法
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}將線程放入等待隊列。
同時計數(shù)器是通過
unlock來-1,所以lock和unlock次數(shù)不匹配就會產生死鎖,也就是當兩個線程調用同一個ReentrantLock如果一個線程中的上鎖解鎖次數(shù)不相等,那么計數(shù)器沒有被清零,當另一個線程請求鎖的時候,看到鎖計數(shù)器不是0,就認為被的線程仍然持有它,所以一直等待它被釋放。需要了解底層的可以去看AQS中的release方法。
而在 ReentrantLock中有一個抽象內部類 Sync,它繼承自抽象類AbstractQueuedSynchronizer(簡稱AQS),這個類中有一個內部 Node類,當有線程等待這把鎖的時候,會創(chuàng)建一個等待隊列,放置這些處于等待的線程。(AQS實現(xiàn)比較復雜,有興趣可以看看“竹子愛熊貓”大佬的文章。)
小結
在ReentrantLock類中,有內部類三個,Sync,FairSync,NonfairSync,他們的關系是Sync是后兩個的父類,后兩個是兄弟類,同時Sync繼承自AQS類,在AQS中有很多實現(xiàn)公平和非公平、可重入的機制,而具體實現(xiàn)效果的是Sync,FairSync,NonfairSync。
疑惑
在下列代碼中,為什么在第一個線程的最后加上.join(),沒有使得線程阻塞,而沒有它就會阻塞?
Lock lock = new ReentrantLock();
new CompletableFuture().runAsync(() -> {
lock.lock();
try{
System.out.println(1);
TimeUnit.SECONDS.sleep(2);
}catch(Exception e){
}finally{
}});
//上面加上.join()
new CompletableFuture().runAsync(() -> {
lock.lock();
try{
System.out.println(2);
}catch(Exception e){
}finally{
lock.unlock();
}}).join();到此這篇關于Java多線程并發(fā)之ReentrantLock的文章就介紹到這了,更多相關ava ReentrantLock內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
mybatis3.4.6 批量更新 foreach 遍歷map 的正確姿勢詳解
這篇文章主要介紹了mybatis3.4.6 批量更新 foreach 遍歷map 的正確姿勢詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11

