java.util.concurrent包中的工具實現(xiàn)核心都是AQS截亦,了解ReentrantLock的實現(xiàn)原理,需要先分析AQS以及AQS與ReentrantLock的關(guān)系雄家。
同步控制中主要使用到的信息如上圖所示癣猾。AQS可以被當做是一個同步監(jiān)視器的實現(xiàn)彼绷,并且具有排隊功能。當線程嘗試獲取AQS的鎖時斋配,如果AQS已經(jīng)被別的線程獲取鎖孔飒,那么將會新建一個Node節(jié)點,并且加入到AQS的等待隊列中艰争,這個隊列也由AQS本身自己維護坏瞄。當鎖被釋放時,喚醒下一個節(jié)點嘗試獲取鎖甩卓。
變量exclusiveOwnerThread在互斥模式下鸠匀,表示當前持有鎖的線程。
變量tail指向等待獲取AQS的鎖的節(jié)點隊列的最后一個
變量head指向隊列中head節(jié)點逾柿,head節(jié)點信息為空缀棍,不表示任何正在等待的線程。
-
變量state表示AQS同步器的狀態(tài)机错,在不同情況下含義可能不太一樣爬范,例如以下幾種
在ReentrantLock中,表示AQS的鎖是否已經(jīng)被占用獲取毡熏,0:沒有坦敌,>=1:
已被獲取,當大于1時表示被同一線程多次重入鎖。在CountDownLatch中痢法,表示計數(shù)還剩的次數(shù)狱窘,當?shù)竭_0時,喚醒等待線程财搁。
在Semaphore中蘸炸,表示AQS還可以被獲取鎖的次數(shù),獲取一次就減1尖奔,當?shù)竭_0時搭儒,嘗試獲取的線程將會阻塞.
二者關(guān)聯(lián)
ReentrantLock實現(xiàn)核心是基于AQS,看下面一張圖,分析AQS與ReentrantLock的關(guān)系提茁。
從圖中可以看出淹禾,ReentrantLock里面有最終兩個內(nèi)部類,F(xiàn)airSync和NonfairSync通過繼承AbstractQueuedSynchronizer的功能茴扁,來實現(xiàn)兩種同步互斥方案:公平鎖和非公平鎖铃岔。在ReentrantLock中最終lock和unlock操作,都由FairSync和NonfairSync實際完成峭火。
- NonfairSync分析
- 下面看一個最簡單利用ReentrantLock實現(xiàn)互斥的例子毁习。
ReentrantLock lock = new ReentrantLock();
//嘗試獲取鎖
lock.lock();
//獲得鎖后執(zhí)行邏輯......
//......
//解鎖
lock.unlock();
- 在ReentrantLock的默認無參構(gòu)造方法中智嚷,創(chuàng)建的是非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
- NonfairSync#lock
下面分析lock.lock();這句代碼是如何實現(xiàn)同步互斥的。
final void lock() {
if (compareAndSetState(0, 1))//【step1】
setExclusiveOwnerThread(Thread.currentThread());//【step2】
else
acquire(1);//【step3】
}
【step1】上面有提到纺且,NonfairSync繼承自AbstractQueuedSynchronizer盏道,NonfairSync就是一個AQS,因此在步驟【1】其實就是利用CAS(一個原子性的比較并設(shè)置操作)嘗試設(shè)置AQS的state為1载碌。如果設(shè)置成功猜嘱,表示獲取鎖成功;如果設(shè)置失敗恐仑,表示state之前已經(jīng)是>=1泉坐,已經(jīng)被別的線程占用了AQS的鎖,所示無法設(shè)置state為1裳仆,稍后會把線程加入到等待隊列腕让。
非公平鎖與公平鎖:對于NonfairSync非公平鎖來說,線程只要執(zhí)行l(wèi)ock請求歧斟,就會立馬嘗試獲取鎖纯丸,不會管AQS當前管理的等待隊列中有沒有正在等待的線程,這種操作是不公平的静袖,沒有先來后到觉鼻;而稍后介紹的FairSync公平鎖,則會在lock請求進行時队橙,先判斷AQS管理的等待隊列中是否已經(jīng)有正在等待的線程坠陈,有的話就是不嘗試獲取鎖,直接進入等待隊列捐康,保證了公平性仇矾。
這一步需要熟悉的是CAS操作,分析一下compareAndSetState源碼解总,如下贮匕。這一步利用unsafe包的cas操作,unsafe包類似一種java留給開發(fā)者的后門花枫,可以用來直接操作內(nèi)存數(shù)據(jù)刻盐,并且保證這個操作的原子性。在下面的代碼中劳翰,stateOffset表示state比變量的內(nèi)存偏移地址敦锌,用來尋找state變量內(nèi)存位置。整個cas操作就是找到內(nèi)存中當前的state變量值佳簸,并且與expect期待值比較乙墙,如果跟期待值相同,那么表示這個值是可以修改的,此時就會對state值進行更新伶丐;如果與期待值不一樣,那么將不能進行更新操作疯特。unsafe保證了比較與設(shè)置值的過程是原子性的哗魂,在這一步不會出現(xiàn)線程安全問題。
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
【step2】操作是在設(shè)置state成功之后漓雅,表示當前線程獲取AQS鎖成功录别,需要設(shè)置AQS中的變量exclusiveOwnerThread為當前持有鎖的線程,做保存記錄邻吞。
【step3】當嘗試獲取鎖失敗的時候组题,就需要進行步驟3,執(zhí)行acquire,進行再次嘗試或者線程進入等待隊列抱冷。
總結(jié)原理流程
當線程1調(diào)用lock方法時崔列,首先看 AQS 的 state 是否為0,如果是0的話旺遮,通過cas操作將state置為1赵讯,并且設(shè)置獨占線程為當前線程
如果這時候線程1 要調(diào)用另外一個lock方法,那么線程1會發(fā)現(xiàn) state = 1耿眉,它再去看獨占線程是不是就是自己边翼,如果是的話 state + 1 ,獲取鎖成功鸣剪。
如果線程1 執(zhí)行的方法還沒有完成即鎖還沒有釋放组底,此時線程2調(diào)用lock方法,由于線程1沒有釋放鎖筐骇,那么state不會等于0驱负,且獨占線程是線程1而不是自己(線程2)留美,所以AQS會把線程2放到等待隊列的尾部,如果線程2的前置結(jié)點是頭結(jié)點head,那么線程2會通過死循環(huán)一直去獲取鎖般甲,如果還是獲取不到鎖,那么會阻塞住線程2彼水,下面的圖有點問題啊条霜。如果不是頭結(jié)點那么就會阻塞線程2,等待線程1釋放鎖且喚醒它悔详。