自旋鎖與自適應自旋及JDK中使用自旋的例子

        互斥同步對性能最大的影響是阻塞的實現,掛起線程和恢復線程的操作都需要轉入內核態中完成,這些操作給系統的併發性能帶來了很大的壓力。 同時,虛擬機的開發團隊也注意到在許多應用上,共享數據的鎖定狀態只會持續很短的一段時間,爲了這段時間去掛起和恢復線程並不值得。 如果物理機器有一個以上的處理器,能讓兩個或以上的線程同時並行執行,我們就可以讓後面請求鎖的那個線程“稍等一下”,但不放棄處理器的執行時間,看看持有鎖的線程是否很快就會釋放鎖。 爲了讓線程等待,我們只需讓線程執行一個忙循環(自旋),這項技術就是所謂的自旋鎖。

        自旋鎖在JDK 1.4.2中就已經引入,只不過默認是關閉的,可以使用-XX:+UseSpinning參數來開啓,在JDK 1.6中就已經改爲默認開啓了。

        自旋等待不能代替阻塞,且先不說對處理器數量的要求,自旋等待本身雖然避免了線程切換的開銷,但它是要佔用處理器時間的,因此,如果鎖被佔用的時間很短,自旋等待的效果就會非常好,反之,如果鎖被佔用的時間很長,那麼自旋的線程只會白白消耗處理器資源,而不會做任何有用的工作,反而會帶來性能上的浪費。 因此,自旋等待的時間必須要有一定的限度,如果自旋超過了限定的次數仍然沒有成功獲得鎖,就應當使用傳統的方式去掛起線程了。 自旋次數的默認值是10次,用戶可以使用參數-XX:PreBlockSpin來更改。

        在JDK 1.6中引入了自適應的自旋鎖。 自適應意味着自旋的時間不再固定了,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。 如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,並且持有鎖的線程正在運行中,那麼虛擬機就會認爲這次自旋也很有可能再次成功,進而它將允許自旋等待持續相對更長的時間,比如100個循環。 另外,如果對於某個鎖,自旋很少成功獲得過,那在以後要獲取這個鎖時將可能省略掉自旋過程,以避免浪費處理器資源。 有了自適應自旋,隨着程序運行和性能監控信息的不斷完善,虛擬機對程序鎖的狀況預測就會越來越準確,虛擬機就會變得越來越“聰明”了。

自旋在JDK中的使用

在抽象同步器中AbstractQueuedSynchronizer

節點在進入同步隊列之後,就進入了一個自旋的過程,每個節點(或者說每個線程)都在自省的觀察,看自己是否滿足條件並獲取到同步狀態;當滿足條件獲取到了同步狀態,就可以從這個自旋過程中退出,否則依舊留在這個自旋過程中,並且會阻塞節點的線程。

互斥模式下獲取同步狀態

//嘗試獲取同步狀態,如果獲取失敗 && 成功添加節點到同步隊列,則進行自我中斷
//tryAcquire(arg)大於等於0表示獲取同步狀態成功,在互斥模式下,只會返回0或1
public final void acquire(int arg) {
     if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}
//同步隊列中的節點自旋獲取同步狀態
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //自旋
            for (;;) {
                //獲取到當前節點的前驅節點
                final Node p = node.predecessor();
                //如果當前節點的前驅節點是head節點(head節點是當前佔有同步狀態的線程)  && 當前成功獲取同步狀態
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //獲取失敗後應該阻塞線程 && 成功阻塞線程並檢查中斷
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
}

獲取共享鎖

//嘗試獲取共享鎖
//tryAcquireShared(arg)返回值大於等於0表示獲取成功
public final void acquireShared(int arg) {
     //如果獲取共享鎖失敗,則進入doAcquireShared(arg)方法
     if (tryAcquireShared(arg) < 0)
         doAcquireShared(arg);
}

//將該節點放入到同步隊列後,進行自旋獲取共享鎖
private void doAcquireShared(int arg) {
        //將節點加入到同步隊列中
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    //嘗試獲取共享鎖
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {//成功獲取共享鎖
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}

 

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