java併發-AQS總結-原理

AQS是Java多線程編程的重入鎖,管程,工具類的基礎類,是必須要掌握的。不掌握這個類,根本不能稱之爲合格的Java程序員。

即使是把這個類所有的代碼都背會,也是值得的。

如何標識已經有線程在執行呢?

有兩個變量,一個state變量,一個exclusiveOwnerThread變量,爲什麼需要兩個變量呢?用一個exclusiveOwnerThread變量不也可以嗎?

state變量用來支持重入,exclusiveOwnerThread變量用來支持互斥。

state變量標識是否已經有線程獲得鎖。但是這裏爲什麼只是用cas嘗試設置一次呢?如下代碼所示:

複製代碼 static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

複製代碼 如果說這裏使用CAS嘗試修改無限循環的設置,那麼能夠就能簡單的實現鎖了呢?如下代碼所示:

final void lock() { int state = getState(); while (!compareAndSetState(state, 1)){

        }
    }        

這裏使用CAS設置state成員變量明顯不行,那能夠設置當前線程呢?這裏是不行的,CAS只能保證多個線程併發修改數據的線程安全特性,只有一個線程修改成功,但無法保證這類數據被一個線程鎖佔用時,另一個線程的CAS操作會失敗的情況!!!所以,這裏只能嘗試設置一次狀態,這裏判斷的用意是看當前線程執行到這裏,有沒有其他的線程已經設置過狀態,若設置過,那麼就只能加入到隊列,否則,自己就直接加鎖不就得了。

acquire方法爲何最後要自我打斷?

分析情況應該分多鐘情況,這樣比較好分析。

情景1:節點類型

1.節點全部都是獨佔模式的情景模擬

2.節點全部都是共享模式的情景模擬

3.節點既有獨佔模式,又有共享模式的情景模擬。

情景2:

1.當有一個線程加入隊列時,此時持有鎖的線程釋放。。。

2.當有2個線程加入隊列時,此時持有鎖的線程釋放。。。

爲什麼會同時存在addWaiter方法和enq方法呢?只使用addWatier方法不就行了嗎?

addWatier方法適用於只有一個線程加入隊列,此時沒有競爭,很快就能加入隊列。

enq方法同時考慮了兩種情況:

1.多個線程都要加入隊列

此時爲了提高效率,先嚐試一次CAS設置,至此有一個線程成功設置,讓其他線程後進入enq方法,讓所有要加入隊列的線程自旋CAS加入隊列。

2.隊列爲空

此時需要初始化一個頭結點,而且,爲了只能設置一個頭結點,運用CAS來操作。

複製代碼 private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } } 複製代碼 這裏比較經典,只有一個線程會走Must initialize,而且只會發生一次!!!然後,大家都會走下面的分支來依次添加到隊尾。思考,這個Head什麼都沒有,如何處置他呢?

我感覺下面會競爭執行權。

結點狀態深入理解!!!結點有好幾種狀態,必須理解一下。

節點狀態有:

1.被取消

-1 等待通知

-2 等待條件

-3 ???

什麼時候會出現被取消的節點呢?

當調用tryLock(TimeUinit)這樣的方法時,如果嘗試獲得鎖失敗,則會將節點變成取消狀態。

什麼時候會出現取消的節點呢?

1.當超過等待時間依然沒有獲得鎖,則退出方法時,會設置成取消狀態

2.當等待過程過,出現打斷異常,則退出方法時,會設置取消狀態

爲什麼會保留這樣的取消的節點呢?

因爲如果要線程自己移除這個取消的節點,還是要保證線程安全,花費的時間是不確定的,所以,不如簡單的設置一下已取消狀態,讓其他的線程幫自己移除,而自己儘快返回,不浪費時間。

使用AQS組件的鎖的線程什麼時候會自旋?

感覺沒有地方會自旋。

爲什麼要用雙向鏈表存儲被阻塞的線程,而不是使用單鏈表?

1.節省尋找前驅節點的時間

爲何ConditionObject是使用單鏈表,而不是雙向鏈表呢?

夠用就好

ReentantLock是否是公平鎖?如果是,如何實現公平鎖的?

通過一個boolean類型參數的構造函數來決定是否公平,公平鎖保證等待的所有線程都有平等的機會獲執行權,實現方式就是有一個線程獲得鎖之後,在這個線程沒有獲得鎖之前,其他線程一直自旋等待加入隊列,公平鎖比較浪費CPU計算資源。此時等待隊列就相當於退化到了只有一個節點的隊列。。。。

非公平鎖,是先來先服務的鎖,誰先能夠加入的等待隊列,則誰就先被通知執行,非公平鎖是阻塞鎖,相較於公平鎖,節省CPU計算資源。

針對這個類的每個方法,都仔細思考思考,切實理解每個方法的實現思路和原因?爲什麼要這樣做比這個方法做了什麼更重要。

每個方法的原因###############

釋放互斥鎖的操作

release方法,內部調用tryRelease方法和unpakcSuccessor(喚醒後繼節點)方法。

tryRelease方法返回true說明完全釋放了鎖,所以其他等待的線程將會調用acquire方法獲得鎖。(其實是正在等待和正在調用acquire方法的線程)

unpackSuccessor方法喚醒後繼節點,這裏比較的有意思。

這個方法先找到第一個,是從雙鏈表的尾部項頭部遍歷,找到哨兵節點的後繼節點,然後unpack操作。

思考,誰把當前的這個節點從鏈表當中移除的呢?因爲此時unpack操作,一定喚醒的是在睡眠的線程,所以,應該是要看做pack操作的後面的代碼,因爲此時會從這裏被喚醒然後繼續執行!!!

從acquireQueued方法可以看出,是線程自己把自己設置爲頭結點???哨兵節點不是頭結點嗎?

自己設置爲頭結點之後,就放棄了成爲等待節點的能力,因爲setHead方法會設置必要的字段爲空,這樣就成爲哨兵節點了。此時,是放棄了之前的哨兵節點了!!!

獲得互斥鎖的操作

可中斷的獲得互斥鎖的操作

超時獲得互斥鎖的操作

嘗試一次獲得互斥鎖的操作

注意:哨兵節點的加入,其實也是惰性的,只有當有線程需要加入阻塞隊列的時候纔會創建哨兵節點,

哨兵節點來自3個操作:

1.第一個要加入阻塞隊列的線程追加的

2.當獲得互斥鎖的線程要釋放互斥鎖,那麼就會unpack後繼節點,後繼節點線程醒來,就會設置自己所在的節點爲哨兵節點,那麼,此時,哨兵節點的status字段就標識了當前線程之前所處的狀態了。

3.釋放共享鎖,跟釋放互斥鎖類似。

#######################################################################################

釋放共享鎖的操作

可中斷獲得共享鎖的操作

獲得共享鎖的操作

超時獲得共享鎖的操作

嘗試一次獲得互斥鎖的操作

##########condition對象

爲什麼condition對象的wait和single類方法使用之前需要加鎖,使用之後需要解鎖?

因爲這類方法都不是線程安全的方法,需要藉助加鎖來保證線程安全,保證自己追加Waiter能夠線程安全。

包括增加等待節點,取消等待節點。

這裏不是線程安全的方法,正好可以藉助ReentantLock來保證線程安全。這樣做,簡化了Condition接口實現類的寫法,比較容易理解。

conditionObject是condition的實現類,作爲一個內部類,用意是能夠訪問外部類對象AQS的一些屬性。

condition對象的本質依然是對等待對列的操作,兩種操作,入隊,出隊,提供等待和通知兩種比較實用的方法的工具組件。

可以實現管程。

condition對象的singleAll是一個一個通知的。先來先通知。

condition構建自己的等待隊列不久OK了嗎?爲何還要讓描述自己的節點加入AQS的同步隊列呢?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章