JUC AQS ReentrantLock源碼分析(一)

Java的內(nèi)置鎖一直都是備受爭議的岸梨,在JDK
1.6之前,synchronized這個重量級鎖其性能一直都是較為低下,雖然在1.6后柜裸,進(jìn)行大量的鎖優(yōu)化策略,但是與Lock相比synchronized還是存在一些缺陷的:雖然synchronized提供了便捷性的隱式獲取鎖釋放鎖機(jī)制(基于JVM機(jī)制)擂错,但是它卻缺少了獲取鎖與釋放鎖的可操作性味滞,可中斷、超時獲取鎖,且它為獨占式在高并發(fā)場景下性能大打折扣剑鞍。

如何自己來實現(xiàn)一個同步

自旋實現(xiàn)一個同步

volatile int status=0;//標(biāo)識---是否有線程在同步塊-----是否有線程上鎖成功
void lock(){
    while(!compareAndSet(0,1)){
    }
    //lock
}
void unlock(){
    status=0;
}
boolean compareAndSet(int except,int newValue){
    //cas操作,修改status成功則返回true
}

缺點:耗費cpu資源昨凡。沒有競爭到鎖的線程會一直占用cpu資源進(jìn)行cas操作,假如一個線程獲得鎖后要花費Ns處理業(yè)務(wù)邏輯蚁署,那另外一個線程就會白白的花費Ns的cpu資源
解決思路:讓得不到鎖的線程讓出CPU

yield+自旋實現(xiàn)同步

volatile int status=0;
void lock(){
    while(!compareAndSet(0,1)){
     yield();//自己實現(xiàn)
    }
    //lock

}
void unlock(){
    status=0;
}

要解決自旋鎖的性能問題必須讓競爭鎖失敗的線程不空轉(zhuǎn),而是在獲取不到鎖的時候能把cpu資源給讓出來便脊,yield()方法就能讓出cpu資源,當(dāng)線程競爭鎖失敗時光戈,會調(diào)用yield方法讓出cpu哪痰。
自旋+yield的方式并沒有完全解決問題,當(dāng)系統(tǒng)只有兩個線程競爭鎖時久妆,yield是有效的晌杰。需要注意的是該方法只是當(dāng)前讓出cpu,有可能操作系統(tǒng)下次還是選擇運行該線程筷弦,比如里面有2000個線程肋演,想想會有什么問題?

sleep+自旋方式實現(xiàn)同步

volatile int status=0;
void lock(){
    while(!compareAndSet(0,1)){
        sleep(10);
    }
    //lock

}
void unlock(){
    status=0;
}

缺點:sleep的時間為什么是10烂琴?怎么控制呢惋啃?很多時候就算你是調(diào)用者本身其實你也不知道這個時間是多少

park+自旋方式實現(xiàn)同步

volatile int status=0;
Queue parkQueue;//集合 數(shù)組  list

void lock(){
    while(!compareAndSet(0,1)){
        //
        park();
    }
    //lock    10分鐘
   。监右。边灭。。健盒。绒瘦。
   unlock()
}

void unlock(){
    lock_notify();
}

void park(){
    //將當(dāng)期線程加入到等待隊列
    parkQueue.add(currentThread);
    //將當(dāng)期線程釋放cpu  阻塞
    releaseCpu();
}
void lock_notify(){
    //得到要喚醒的線程頭部線程
    Thread t=parkQueue.header();
    //喚醒等待線程
    unpark(t);
}

這種方法就比較完美,當(dāng)然我寫的都偽代碼扣癣,我看看大師是如何利用這種機(jī)制來實現(xiàn)同步的惰帽;JDK的JUC包下面ReentrantLock類的原理就是利用了這種機(jī)制;

ReentrantLock源碼分析之上鎖過程

AQS(AbstractQueuedSynchronizer)類的設(shè)計主要代碼(具體參考源碼)

private transient volatile Node head; //隊首
private transient volatile Node tail;//尾
private volatile int state;//鎖狀態(tài)父虑,加鎖成功則為1该酗,重入+1 解鎖則為0

AQS當(dāng)中的隊列示意圖

在這里插入圖片描述

Node類的設(shè)計

public class Node{
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
}

上鎖過程重點

鎖對象:其實就是ReentrantLock的實例對象,下文應(yīng)用代碼第一行中的lock對象就是所謂的鎖
自由狀態(tài):自由狀態(tài)表示鎖對象沒有被別的線程持有士嚎,計數(shù)器為0
計數(shù)器:再lock對象中有一個字段state用來記錄上鎖次數(shù)呜魄,比如lock對象是自由狀態(tài)則state為0,如果大于零則表示被線程持有了莱衩,當(dāng)然也有重入那么state則>1
waitStatus:僅僅是一個狀態(tài)而已爵嗅;ws是一個過渡狀態(tài),在不同方法里面判斷ws的狀態(tài)做不同的處理笨蚁,所以ws=0有其存在的必要性
tail:隊列的隊尾 head:隊列的對首 ts:第二個給lock加鎖的線程 tf:第一個給lock加鎖的線程 tc:當(dāng)前給線程加鎖的線程
tl:最后一個加鎖的線程 tn:隨便某個線程
當(dāng)然這些線程有可能重復(fù)睹晒,比如第一次加鎖的時候tf=tc=tl=tn
節(jié)點:就是上面的Node類的對象趟庄,里面封裝了線程,所以某種意義上node就等于一個線程

首先一個簡單的應(yīng)用

 final ReentrantLock lock = new ReentrantLock(true);
 Thread t1= new Thread("t1"){
     @Override
     public void run() {
         lock.lock();
         logic();
         lock.unlock();
     }
 };
t1.start();

公平鎖lock方法的源碼分析

final void lock() {
    acquire(1);//1------標(biāo)識加鎖成功之后改變的值
}

非公平鎖的looc方法

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
         acquire(1);
} 

下面給出他們的代碼執(zhí)行邏輯的區(qū)別圖

在這里插入圖片描述

公平鎖的上鎖是必須判斷自己是不是需要排隊伪很;而非公平鎖是直接進(jìn)行CAS修改計數(shù)器看能不能加鎖成功戚啥;如果加鎖不成功則乖乖排隊(調(diào)用acquire);所以不管公平還是不公平锉试;只要進(jìn)到了AQS隊列當(dāng)中那么他就會排隊猫十;一朝排隊;永遠(yuǎn)排隊記住這點

acquire方法方法源碼分析

public final void acquire(int arg) {
    //tryAcquire(arg)嘗試加鎖键痛,如果加鎖失敗則會調(diào)用acquireQueued方法加入隊列去排隊炫彩,如果加鎖成功則不會調(diào)用
    //acquireQueued方法下文會有解釋
    //加入隊列之后線程會立馬park匾七,等到解鎖之后會被unpark絮短,醒來之后判斷自己是否被打斷了;被打斷下次分析
    //為什么需要執(zhí)行這個方法昨忆?下文解釋
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

acquire方法首先會調(diào)用tryAcquire方法丁频,注意tryAcquire的結(jié)果做了取反

tryAcquire方法源碼分析
protected final boolean tryAcquire(int acquires) {
    //獲取當(dāng)前線程
    final Thread current = Thread.currentThread();
    //獲取lock對象的上鎖狀態(tài),如果鎖是自由狀態(tài)則=0邑贴,如果被上鎖則為1席里,大于1表示重入
    int c = getState();
    if (c == 0) {//沒人占用鎖--->我要去上鎖----1、鎖是自由狀態(tài)
        //hasQueuedPredecessors拢驾,判斷自己是否需要排隊這個方法比較復(fù)雜奖磁,
        //下面我會單獨介紹,如果不需要排隊則進(jìn)行cas嘗試加鎖繁疤,如果加鎖成功則把當(dāng)前線程設(shè)置為擁有鎖的線程
        //繼而返回true
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            //設(shè)置當(dāng)前線程為擁有鎖的線程咖为,方面后面判斷是不是重入(只需把這個線程拿出來判斷是否當(dāng)前線程即可判斷重入)    
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //如果C不等于0,而且當(dāng)前線程不等于擁有鎖的線程則不會進(jìn)else if 直接返回false稠腊,加鎖失敗
    //如果C不等于0躁染,但是當(dāng)前線程等于擁有鎖的線程則表示這是一次重入,那么直接把狀態(tài)+1表示重入次數(shù)+1
    //那么這里也側(cè)面說明了reentrantlock是可以重入的架忌,因為如果是重入也返回true吞彤,也能lock成功
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

hasQueuedPredecessors判斷是否需要排隊的源碼分析

這里需要記住一點,整個方法如果最后返回false叹放,則去加鎖饰恕,如果返回true則不加鎖,因為這個方法被取反了

public final boolean hasQueuedPredecessors() {
    Node t = tail; 
    Node h = head;
    Node s;
    /**
     * 下面提到的所有不需要排隊井仰,并不是字面意義懂盐,我實在想不出什么詞語來描述這個“不需要排隊”;不需要排隊有兩種情況
     * 一:隊列沒有初始化糕档,不需要排隊莉恼,不需要排隊拌喉,不需要排隊;直接去加鎖俐银,但是可能會失斈虮场;為什么會失敗呢捶惜?
     * 假設(shè)兩個線程同時來lock田藐,都看到隊列沒有初始化,都認(rèn)為不需要排隊吱七,都去進(jìn)行CAS修改計數(shù)器汽久;有一個必然失敗
     * 比如t1先拿到鎖,那么另外一個t2則會CAS失敗踊餐,這個時候t2就會去初始化隊列景醇,并排隊
     *
     * 二:隊列被初始化了,但是tc過來加鎖吝岭,發(fā)覺隊列當(dāng)中第一個排隊的就是自己三痰;比如重入;
     * 那么什么叫做第一個排隊的呢窜管?下面解釋了散劫,很重要往下看;
     * 這個時候他也不需要排隊幕帆,不需要排隊获搏,不需要排隊;為什么不需要排對失乾?
     * 因為隊列當(dāng)中第一個排隊的線程他會去嘗試獲取一下鎖常熙,因為有可能這個時候持有鎖鎖的那個線程可能釋放了鎖;
     * 如果釋放了就直接獲取鎖執(zhí)行仗扬。但是如果沒有釋放他就會去排隊症概,
     * 所以這里的不需要排隊,不是真的不需要排隊
     *
     * h != t 判斷首不等于尾這里要分三種情況
     * 1早芭、隊列沒有初始化彼城,也就是第一個線程tf來加鎖的時候那么這個時候隊列沒有初始化,
     * h和t都是null退个,那么這個時候判斷不等于則不成立(false)那么由于是&&運算后面的就不會走了募壕,
     * 直接返回false表示不需要排隊,而前面又是取反(if (!hasQueuedPredecessors())语盈,所以會直接去cas加鎖舱馅。
     * ----------第一種情況總結(jié):隊列沒有初始化沒人排隊,那么我直接不排隊刀荒,直接上鎖代嗤;合情合理棘钞、有理有據(jù)令人信服;
     * 好比你去火車站買票干毅,服務(wù)員都閑的蛋疼宜猜,整個隊列都沒有形成;沒人排隊硝逢,你直接過去交錢拿票
     *
     * 2姨拥、隊列被初始化了,后面會分析隊列初始化的流程渠鸽,如果隊列被初始化那么h!=t則成立叫乌;(不絕對,還有第3中情況)
     * h != t 返回true徽缚;但是由于是&&運算憨奸,故而代碼還需要進(jìn)行后續(xù)的判斷
     * (有人可能會疑問,比如隊列初始化了猎拨;里面只有一個數(shù)據(jù)膀藐,那么頭和尾都是同一個怎么會成立呢屠阻?
     * 其實這是第3種情況--對頭等于對尾红省;但是這里先不考慮,我們假設(shè)現(xiàn)在隊列里面有大于1個數(shù)據(jù))
     * 大于1個數(shù)據(jù)則成立;繼續(xù)判斷把h.next賦值給s国觉;s有是對頭的下一個Node吧恃,
     * 這個時候s則表示他是隊列當(dāng)中參與排隊的線程而且是排在最前面的;
     * 為什么是s最前面不是h嘛麻诀?誠然h是隊列里面的第一個痕寓,但是不是排隊的第一個;下文有詳細(xì)解釋
     * 因為h也就是對頭對應(yīng)的Node對象或者線程他是持有鎖的蝇闭,但是不參與排隊呻率;
     * 這個很好理解,比如你去買車票呻引,你如果是第一個這個時候售票員已經(jīng)在給你服務(wù)了礼仗,你不算排隊,你后面的才算排隊逻悠;
     * 隊列里面的h是不參與排隊的這點一定要明白元践;參考下面關(guān)于隊列初始化的解釋;
     * 因為h要么是虛擬出來的節(jié)點童谒,要么是持有鎖的節(jié)點单旁;什么時候是虛擬的呢?什么時候是持有鎖的節(jié)點呢饥伊?下文分析
     * 然后判斷s是否等于空象浑,其實就是判斷隊列里面是否只有一個數(shù)據(jù)蔫饰;
     * 假設(shè)隊列大于1個,那么肯定不成立(s==null---->false)愉豺,因為大于一個Node的時候h.next肯定不為空死嗦;
     * 由于是||運算如果返回false,還要判斷s.thread != Thread.currentThread()粒氧;這里又分為兩種情況
     *        2.1 s.thread != Thread.currentThread() 返回true越除,就是當(dāng)前線程不等于在排隊的第一個線程s;
     *              那么這個時候整體結(jié)果就是h!=t:true; (s==null false || s.thread != Thread.currentThread() true  最后true)
     *              結(jié)果: true && true 方法最終放回true外盯,所以需要去排隊
     *              其實這樣符合情理摘盆,試想一下買火車票,隊列不為空饱苟,有人在排隊孩擂;
     *              而且第一個排隊的人和現(xiàn)在來參與競爭的人不是同一個,那么你就乖乖去排隊
     *        2.2 s.thread != Thread.currentThread() 返回false 表示當(dāng)前來參與競爭鎖的線程和第一個排隊的線程是同一個線程
     *             這個時候整體結(jié)果就是h!=t---->true; (s==null false || s.thread != Thread.currentThread() false-----> 最后false)
     *            結(jié)果:true && false 方法最終放回false箱熬,所以不需要去排隊
     *            不需要排隊則調(diào)用 compareAndSetState(0, acquires) 去改變計數(shù)器嘗試上鎖类垦;
     *            這里又分為兩種情況(日了狗了這一行代碼;有同學(xué)課后反應(yīng)說子路老師老師老是說這個AQS難城须,
     *            你現(xiàn)在仔細(xì)看看這一行代碼的意義蚤认,真的不簡單的)
     *             2.2.1  第一種情況加鎖成功?有人會問為什么會成功啊糕伐,如這個時候h也就是持有鎖的那個線程執(zhí)行完了
     *                      釋放鎖了砰琢,那么肯定成功啊良瞧;成功則執(zhí)行 setExclusiveOwnerThread(current); 然后返回true 自己看代碼
     *             2.2.2  第二種情況加鎖失斉闫?有人會問為什么會失敗啊褥蚯。假如這個時候h也就是持有鎖的那個線程沒執(zhí)行完
     *                       沒釋放鎖挚冤,那么肯定失敗啊赞庶;失敗則直接返回false训挡,不會進(jìn)else if(else if是相對于 if (c == 0)的)
     *                      那么如果失敗怎么辦呢?后面分析尘执;
     *
     *----------第二種情況總結(jié)舍哄,如果隊列被初始化了,而且至少有一個人在排隊那么自己也去排隊誊锭;但是有個插曲表悬;
     * ----------他會去看看那個第一個排隊的人是不是自己,如果是自己那么他就去嘗試加鎖丧靡;嘗試看看鎖有沒有釋放
     *----------也合情合理蟆沫,好比你去買票籽暇,如果有人排隊,那么你乖乖排隊饭庞,但是你會去看第一個排隊的人是不是你女朋友戒悠;
     *----------如果是你女朋友就相當(dāng)于是你自己(這里實在想不出現(xiàn)實世界關(guān)于重入的例子,只能用男女朋友來替代)舟山;
     * --------- 你就叫你女朋友看看售票員有沒有搞完绸狐,有沒有輪到你女朋友,因為你女朋友是第一個排隊的
     * 疑問:比如如果在在排隊累盗,那么他是park狀態(tài)寒矿,如果是park狀態(tài),自己怎么還可能重入啊若债。
     * 希望有同學(xué)可以想出來為什么和我討論一下符相,作為一個菜逼,希望有人教教我
     *  
     * 
     * 3蠢琳、隊列被初始化了啊终,但是里面只有一個數(shù)據(jù);什么情況下才會出現(xiàn)這種情況呢傲须?ts加鎖的時候里面就只有一個數(shù)據(jù)蓝牲?
     * 其實不是,因為隊列初始化的時候會虛擬一個h作為頭結(jié)點躏碳,tc=ts作為第一個排隊的節(jié)點搞旭;tf為持有鎖的節(jié)點
     * 為什么這么做呢散怖?因為AQS認(rèn)為h永遠(yuǎn)是不排隊的菇绵,假設(shè)你不虛擬節(jié)點出來那么ts就是h哥纫,
     *  而ts其實需要排隊的樊诺,因為這個時候tf可能沒有執(zhí)行完,還持有著鎖臀突,ts得不到鎖欠动,故而他需要排隊永乌;
     * 那么為什么要虛擬為什么ts不直接排在tf之后呢,上面已經(jīng)時說明白了具伍,tf來上鎖的時候隊列都沒有翅雏,他不進(jìn)隊列,
     * 故而ts無法排在tf之后人芽,只能虛擬一個thread=null的節(jié)點出來(Node對象當(dāng)中的thread為null)望几;
     * 那么問題來了;究竟什么時候會出現(xiàn)隊列當(dāng)中只有一個數(shù)據(jù)呢萤厅?假設(shè)原隊列里面有5個人在排隊橄抹,當(dāng)前面4個都執(zhí)行完了
     * 輪到第五個線程得到鎖的時候靴迫;他會把自己設(shè)置成為頭部,而尾部又沒有楼誓,故而隊列當(dāng)中只有一個h就是第五個
     * 至于為什么需要把自己設(shè)置成頭部玉锌;其實已經(jīng)解釋了,因為這個時候五個線程已經(jīng)不排隊了疟羹,他拿到鎖了主守;
     * 所以他不參與排隊,故而需要設(shè)置成為h榄融;即頭部丸逸;所以這個時間內(nèi),隊列當(dāng)中只有一個節(jié)點
     * 關(guān)于加鎖成功后把自己設(shè)置成為頭部的源碼剃袍,后面會解析到黄刚;繼續(xù)第三種情況的代碼分析
     * 記得這個時候隊列已經(jīng)初始化了,但是只有一個數(shù)據(jù)民效,并且這個數(shù)據(jù)所代表的線程是持有鎖
     * h != t false 由于后面是&&運算憔维,故而返回false可以不參與運算,整個方法返回false畏邢;不需要排隊
     *
     *
     *-------------第三種情況總結(jié):如果隊列當(dāng)中只有一個節(jié)點业扒,而這種情況我們分析了,
     *-------------這個節(jié)點就是當(dāng)前持有鎖的那個節(jié)點舒萎,故而我不需要排隊程储,進(jìn)行cas;嘗試加鎖
     *-------------這是AQS的設(shè)計原理臂寝,他會判斷你入隊之前章鲤,隊列里面有沒有人排隊;
     *-------------有沒有人排隊分兩種情況咆贬;隊列沒有初始化败徊,不需要排隊
     *--------------隊列初始化了,按時只有一個節(jié)點掏缎,也是沒人排隊皱蹦,自己先也不排隊
     *--------------只要認(rèn)定自己不需要排隊,則先嘗試加鎖眷蜈;加鎖失敗之后再排隊沪哺;
     *--------------再一次解釋了不需要排隊這個詞的歧義性
     *-------------如果加鎖失敗了,在去park酌儒,下文有詳細(xì)解釋這樣設(shè)計源碼和原因
     *-------------如果持有鎖的線程釋放了鎖辜妓,那么我能成功上鎖
     *
     **/
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

到此我們已經(jīng)解釋完了!tryAcquire(arg)方法,為了方便我再次貼一下代碼

public final void acquire(int arg) {
    //tryAcquire(arg)嘗試加鎖,如果加鎖失敗則會調(diào)用acquireQueued方法加入隊列去排隊嫌拣,如果加鎖成功則不會調(diào)用
    //acquireQueued方法下文會有解釋
    //加入隊列之后線程會立馬park柔袁,等到解鎖之后會被unpark,醒來之后判斷自己是否被打斷了
    //為什么需要執(zhí)行這個方法异逐?下次解釋
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

acquireQueued(addWaiter(Node.exclusive),arg))方法解析

如果代碼能執(zhí)行到這里說tc需要排隊
需要排隊有兩種情況—換言之代碼能夠執(zhí)行到這里有兩種情況:
1捶索、tf持有了鎖,并沒有釋放灰瞻,所以tc來加鎖的時候需要排隊腥例,但這個時候—隊列并沒有初始化
2、tn(無所謂哪個線程酝润,反正就是一個線程)持有了鎖燎竖,那么由于加鎖tn!=tf(tf是屬于第一種情況,我們現(xiàn)在不考慮tf了)要销,所以隊列是一定被初始化了的构回,tc來加鎖,那么隊列當(dāng)中有人在排隊疏咐,故而他也去排隊

addWaiter(Node.EXCLUSIVE)源碼分析
private Node addWaiter(Node mode) {
    //由于AQS隊列當(dāng)中的元素類型為Node纤掸,故而需要把當(dāng)前線程tc封裝成為一個Node對象,下文我們叫做nc
    Node node = new Node(Thread.currentThread(), mode);
    //tail為對尾,賦值給pred 
    Node pred = tail;
    //判斷pred是否為空浑塞,其實就是判斷對尾是否有節(jié)點借跪,其實只要隊列被初始化了對尾肯定不為空,
    //假設(shè)隊列里面只有一個元素酌壕,那么對尾和對首都是這個元素
    //換言之就是判斷隊列有沒有初始化
    //上面我們說過代碼執(zhí)行到這里有兩種情況掏愁,1、隊列沒有初始化和2卵牍、隊列已經(jīng)初始化了
    //pred不等于空表示第二種情況果港,隊列被初始化了,如果是第二種情況那比較簡單
   //直接把當(dāng)前線程封裝的nc的上一個節(jié)點設(shè)置成為pred即原來的對尾
   //繼而把pred的下一個節(jié)點設(shè)置為當(dāng)nc辽慕,這個nc自己成為對尾了
    if (pred != null) {
        //直接把當(dāng)前線程封裝的nc的上一個節(jié)點設(shè)置成為pred即原來的對尾京腥,對應(yīng) 10行的注釋
        node.prev = pred;
        //這里需要cas,因為防止多個線程加鎖溅蛉,確保nc入隊的時候是原子操作
        if (compareAndSetTail(pred, node)) {
            //繼而把pred的下一個節(jié)點設(shè)置為當(dāng)nc,這個nc自己成為對尾了 對應(yīng)第11行注釋
            pred.next = node;
            //然后把nc返回出去他宛,方法結(jié)束
            return node;
        }
    }
    //如果上面的if不成了就會執(zhí)行到這里船侧,表示第一種情況隊列并沒有初始化---下面解析這個方法
    enq(node);
    //返回nc
    return node;
}

private Node enq(final Node node) {//這里的node就是當(dāng)前線程封裝的node也就是nc
    //死循環(huán)
    for (;;) {
        //對尾復(fù)制給t,上面已經(jīng)說過隊列沒有初始化厅各,
        //故而第一次循環(huán)t==null(因為是死循環(huán)镜撩,因此強(qiáng)調(diào)第一次,后面可能還有第二次、第三次袁梗,每次t的情況肯定不同)
        Node t = tail;
        //第一次循環(huán)成了成立
        if (t == null) { // Must initialize
            //new Node就是實例化一個Node對象下文我們稱為nn宜鸯,
            //調(diào)用無參構(gòu)造方法實例化出來的Node里面三個屬性都為null,可以關(guān)聯(lián)Node類的結(jié)構(gòu)遮怜,
            //compareAndSetHead入隊操作淋袖;把這個nn設(shè)置成為隊列當(dāng)中的頭部,cas防止多線程锯梁、確保原子操作即碗;
            //記住這個時候隊列當(dāng)中只有一個,即nn
            if (compareAndSetHead(new Node()))
                //這個時候AQS隊列當(dāng)中只有一個元素陌凳,即頭部=nn剥懒,所以為了確保隊列的完整,設(shè)置頭部等于尾部合敦,即nn即是頭也是尾
                //然后第一次循環(huán)結(jié)束初橘;接著執(zhí)行第二次循環(huán),第二次循環(huán)代碼我寫在了下面充岛,接著往下看就行
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

//為了方便 第二次循環(huán)我再貼一次代碼來對第二遍循環(huán)解釋
private Node enq(final Node node) {//這里的node就是當(dāng)前線程封裝的node也就是nc
    //死循環(huán)
    for (;;) {
        //對尾復(fù)制給t壁却,由于第二次循環(huán),故而tail==nn裸准,即new出來的那個node
        Node t = tail;
        //第二次循環(huán)不成立
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //不成立故而進(jìn)入else
            //首先把nc展东,當(dāng)前線程所代表的的node的上一個節(jié)點改變?yōu)閚n,因為這個時候nc需要入隊炒俱,入隊的時候需要把關(guān)系維護(hù)好
            //所謂的維護(hù)關(guān)系就是形成鏈表盐肃,nc的上一個節(jié)點只能為nn,這個很好理解
            node.prev = t;
            //入隊操作--把nc設(shè)置為對尾权悟,對首是nn砸王,
            if (compareAndSetTail(t, node)) {
                //上面我們說了為了維護(hù)關(guān)系把nc的上一個節(jié)點設(shè)置為nn
                //這里同樣為了維護(hù)關(guān)系,把nn的下一個節(jié)點設(shè)置為nc
                t.next = node;
                //然后返回t峦阁,即nn谦铃,死循環(huán)結(jié)束,enq(node);方法返回
                //這個返回其實就是為了終止循環(huán)榔昔,返回出去的t驹闰,沒有意義
                return t;
            }
        }
    }
}

  //這個方法已經(jīng)解釋完成了
  enq(node);
  //返回nc,不管哪種情況都會返回nc撒会;到此addWaiter方法解釋完成
  return node;

//再次貼出node的結(jié)構(gòu)方便大家查看
public class Node{
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
}

-------------------總結(jié):addWaiter方法就是讓nc入隊-并且維護(hù)隊列的鏈表關(guān)系嘹朗,但是由于情況復(fù)雜做了不同處理
-------------------主要針對隊列是否有初始化,沒有初始化則new一個新的Node nn作為對首诵肛,nn里面的線程為null
-------------------接下來分析acquireQueued方法

acquireQueued(addWaiter(Node.exclusive),arg))經(jīng)過上面的解析之后可以理解成為acquireQueued(nc,arg))

acquireQueued方法的源碼分析
final boolean acquireQueued(final Node node, int arg) {//這里的node 就是當(dāng)前線程封裝的那個node 下文叫做nc
    //記住標(biāo)志很重要
    boolean failed = true;
    try {
        //同樣是一個標(biāo)志
        boolean interrupted = false;
        //死循環(huán)
        for (;;) {
            //獲取nc的上一個節(jié)點屹培,有兩種情況;1、上一個節(jié)點為頭部褪秀;2上一個節(jié)點不為頭部
            final Node p = node.predecessor();
            //如果nc的上一個節(jié)點為頭部蓄诽,則表示nc為隊列當(dāng)中的第二個元素,為隊列當(dāng)中的第一個排隊的Node媒吗;
            //這里的第一和第二不沖突仑氛;我上文有解釋;
            //如果nc為隊列當(dāng)中的第二個元素蝴猪,第一個排隊的則調(diào)用tryAcquire去嘗試加鎖---關(guān)于tryAcquire看上面的分析
            //只有nc為第二個元素调衰;第一個排隊的情況下才會嘗試加鎖,其他情況直接去park了自阱,
            //因為第一個排隊的執(zhí)行到這里的時候需要看看持有有鎖的線程有沒有釋放鎖嚎莉,釋放了就輪到我了,就不park了
            //有人會疑惑說開始調(diào)用tryAcquire加鎖失敗了(需要排隊)沛豌,這里為什么還要進(jìn)行tryAcquire不是重復(fù)了嗎趋箩?
            //其實不然,因為第一次tryAcquire判斷是否需要排隊加派,如果需要排隊叫确,那么我就入隊;
            //當(dāng)我入隊之后我發(fā)覺前面那個人就是第一個芍锦,持有鎖的那個竹勉,那么我不死心,再次問問前面那個人搞完沒有
            //如果他搞完了娄琉,我就不park次乓,接著他搞我自己的事;如果他沒有搞完孽水,那么我則在隊列當(dāng)中去park票腰,等待別人叫我
            //但是如果我去排隊,發(fā)覺前面那個人在睡覺女气,前面那個人都在睡覺杏慰,那么我也睡覺把---------------好好理解一下
            if (p == head && tryAcquire(arg)) {
                //能夠執(zhí)行到這里表示我來加鎖的時候,鎖被持有了炼鞠,我去排隊缘滥,進(jìn)到隊列當(dāng)中的時候發(fā)覺我前面那個人沒有park,
                //前面那個人就是當(dāng)前持有鎖的那個人簇搅,那么我問問他搞完沒有
                //能夠進(jìn)到這個里面就表示前面那個人搞完了完域;所以這里能執(zhí)行到的幾率比較小瘩将;但是在高并發(fā)的世界中這種情況真的需要考慮
                //如果我前面那個人搞完了,我nc得到鎖了,那么前面那個人直接出隊列姿现,我自己則是對首肠仪;這行代碼就是設(shè)置自己為對首
                setHead(node);
                //這里的P代表的就是剛剛搞完事的那個人,由于他的事情搞完了备典,要出隊异旧;怎么出隊?把鏈表關(guān)系刪除
                p.next = null; // help GC
                //設(shè)置表示---記住記加鎖成功的時候為false
                failed = false;
                //返回false提佣;為什么返回false吮蛹?下次博客解釋---比較復(fù)雜和加鎖無關(guān)
                return interrupted;
            }
            //進(jìn)到這里分為兩種情況
            //1、nc的上一個節(jié)點不是頭部拌屏,說白了潮针,就是我去排隊了,但是我上一個人不是隊列第一個
            //2倚喂、第二種情況每篷,我去排隊了,發(fā)覺上一個節(jié)點是第一個端圈,但是他還在搞事沒有釋放鎖
            //不管哪種情況這個時候我都需要park焦读,park之前我需要把上一個節(jié)點的狀態(tài)改成park狀態(tài)
            //這里比較難以理解為什么我需要去改變上一個節(jié)點的park狀態(tài)呢?每個node都有一個狀態(tài)舱权,默認(rèn)為0矗晃,表示無狀態(tài)
            //-1表示在park;當(dāng)時不能自己把自己改成-1狀態(tài)宴倍?為什么呢张症?因為你得確定你自己park了才是能改為-1;
            //不然你自己改成自己為-1啊楚;但是改完之后你沒有park那不就騙人吠冤?
            //你對外宣布自己是單身狀態(tài),但是實際和劉宏斌私下約會恭理;這有點坑人
            //所以只能先park拯辙;在改狀態(tài);但是問題你自己都park了颜价;完全釋放CPU資源了涯保,故而沒有辦法執(zhí)行任何代碼了,
            //所以只能別人來改周伦;故而可以看到每次都是自己的后一個節(jié)點把自己改成-1狀態(tài)
            //關(guān)于shouldParkAfterFailedAcquire這個方法的源碼下次博客繼續(xù)講吧
            if (shouldParkAfterFailedAcquire(p, node) &&
                //改上一個節(jié)點的狀態(tài)成功之后夕春;自己park;到此加鎖過程說完了
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

加鎖過程總結(jié):

如果是第一個線程tf专挪,那么和隊列無關(guān)及志,線程直接持有鎖片排。并且也不會初始化隊列,如果接下來的線程都是交替執(zhí)行速侈,那么永遠(yuǎn)和AQS隊列無關(guān)率寡,都是直接線程持有鎖.。

如果發(fā)生了競爭倚搬,比如tf持有鎖的過程中T2來lock冶共,那么這個時候就會初始化AQS,初始化AQS的時候會在隊列的頭部虛擬一個Thread為NULL的Node每界,因為隊列當(dāng)中的head永遠(yuǎn)是持有鎖的那個node(除了第一次會虛擬一個捅僵,其他時候都是持有鎖的那個線程鎖封裝的node)。

現(xiàn)在第一次的時候持有鎖的是tf而tf不在隊列當(dāng)中所以虛擬了一個node節(jié)點眨层,隊列當(dāng)中的除了head之外的所有的node都在park庙楚,當(dāng)tf釋放鎖之后unpark某個(基本是隊列當(dāng)中的第二個,為什么是第二個呢谐岁?前面說過head永遠(yuǎn)是持有鎖的那個node醋奠,當(dāng)有時候也不會是第二個,比如第二個被cancel之后伊佃,至于為什么會被cancel窜司,不在我們討論范圍之內(nèi),cancel的條件很苛刻航揉,基本不會發(fā)生)node之后塞祈;

node被喚醒,假設(shè)node是t2帅涂,那么這個時候會首先把t2變成head(sethead)议薪,在sethead方法里面會把t2代表的node設(shè)置為head,并且把node的Thread設(shè)置為null媳友,為什么需要設(shè)置null斯议?

其實原因很簡單,現(xiàn)在t2已經(jīng)拿到鎖了醇锚,node就不要排隊了哼御,那么node對Thread的引用就沒有意義了。所以隊列的head里面的Thread永遠(yuǎn)為null

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末焊唬,一起剝皮案震驚了整個濱河市恋昼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赶促,老刑警劉巖液肌,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鸥滨,居然都是意外死亡嗦哆,警方通過查閱死者的電腦和手機(jī)谤祖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吝秕,“玉大人泊脐,你說我怎么就攤上這事空幻∷盖停” “怎么了?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵秕铛,是天一觀的道長约郁。 經(jīng)常有香客問我,道長但两,這世上最難降的妖魔是什么鬓梅? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮谨湘,結(jié)果婚禮上绽快,老公的妹妹穿的比我還像新娘。我一直安慰自己紧阔,他們只是感情好坊罢,可當(dāng)我...
    茶點故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著擅耽,像睡著了一般活孩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乖仇,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天憾儒,我揣著相機(jī)與錄音,去河邊找鬼乃沙。 笑死起趾,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的警儒。 我是一名探鬼主播训裆,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼冷蚂!你這毒婦竟也來了缭保?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蝙茶,失蹤者是張志新(化名)和其女友劉穎艺骂,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隆夯,經(jīng)...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡钳恕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年别伏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片忧额。...
    茶點故事閱讀 40,435評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡厘肮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出睦番,到底是詐尸還是另有隱情类茂,我是刑警寧澤,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布托嚣,位于F島的核電站巩检,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏示启。R本人自食惡果不足惜兢哭,卻給世界環(huán)境...
    茶點故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夫嗓。 院中可真熱鬧迟螺,春花似錦、人聲如沸舍咖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谎仲。三九已至浙垫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間郑诺,已是汗流浹背夹姥。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留辙诞,地道東北人辙售。 一個月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像飞涂,于是被迫代替她去往敵國和親旦部。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,442評論 2 359

推薦閱讀更多精彩內(nèi)容