1. 程式人生 > >Java併發程式設計-佇列同步器(AbstractQueuedSynchronizer)

Java併發程式設計-佇列同步器(AbstractQueuedSynchronizer)

章節目錄

  • Lock介面與Synchronized的區別及特性
  • 佇列同步器的介面與自定義鎖示例
  • 佇列同步器的實現分析

1.Lock介面與Synchronized的區別及特性

特性 描述
嘗試非阻塞性的獲取鎖 當前執行緒嘗試獲取鎖(自旋獲取鎖),如果這一時刻鎖沒有被其他執行緒獲取到,則成功獲取並持有鎖
能被中斷的獲取鎖 已獲取鎖的執行緒可以響應中斷,當獲取到鎖的執行緒被中斷時,可以丟擲中斷異常,同時鎖會被釋放
超時獲取鎖 在指定的截止時間之前獲取鎖,如果截止時間到了仍然沒有獲取到鎖,則返回

注意:Lock介面的實現基本上都是通過聚合了一個同步器的子類來完成執行緒訪問控制的

隊裡同步器的介面與定義鎖示例

佇列同步器定義:

佇列同步器,是用來構建鎖與其它同步元件的基礎框架,基本資料結構與內容是:
1、int state -> state 標示同步狀態;
2、內建的FIFO來完成獲取同步狀態的執行緒的排隊工作。

佇列同步器使用方式

1、子類通過繼承同步器並實現它的抽象方法來管理同步狀態;
2、實現過程中對同步狀態的更改,通過
setState()、
setState(int newState)、
compareAndSetState(int expect,int newUpdateValue)
來進行操作,保證狀態改變時原子性的、安全的;
3、實現同步器的子類被推薦為自定義同步元件的靜態內部類;
4、同步器可以支援獨佔式的獲取同步狀態(ReentrantLock)、也可以支援共享
式的獲取同步狀態(ReentrantReadWriteLock)

對於同步器的關係可以這樣理解:

  • 在鎖的實現中聚合同步器,利用同步器實現鎖的語義。
  • 鎖面向使用者,它定義了使用者與鎖的互動介面,隱藏了實現細節。
  • 同步器面向的是鎖的實現者,它簡化了鎖的實現方式,遮蔽了同步器狀態管理、執行緒排隊、等待與喚醒等底層操作。

2.佇列同步器的介面與自定義鎖示例

2.1 模板方法模式

同步器的設定是基於**模版方法模式**,使用者需要繼承同步器並重寫指定的方
法,隨後將同步器組合在自定義同步元件的實現中,並呼叫同步器提供的模板
方法,而這些模板方法將會呼叫使用者重寫的方法。

2.2 重寫同步器指定的方法

getState():獲取當前同步狀態
setState(int newState):設定當前同步狀態
compareAndSetState(int expect,int update): 使用CAS設定當前的狀態,該方
法保證狀態設定的原子性

2.3 同步器可重寫的方法

方法名稱 描述
protected boolean tryAcquire(int arg) 獨佔式獲取同步狀態,實現該方法需要查詢當前狀態並判斷同步狀態是否符合預期,然後再進行CAS設定同步狀態
protected boolean tryRelease(int arg) 獨佔式釋放同步狀態,等待獲取同步狀態的執行緒將有機會獲取同步狀態(公平性獲取鎖)
protected int tryAcquireShared(int arg) 共享式獲取同步狀態,返回>=0的值,標示獲取成功,反之獲取失敗
protected boolean tryReleaseShared(int arg) 共享式釋放同步狀態
protected boolean isHeldExclusively() 當前同步器是否在獨佔模式下被執行緒佔用,一般該方法表示是否被當前執行緒所獨佔

2.4 獨佔鎖示例

package org.seckill.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * 利用了模板方法模式
 */
public class Mutex implements Lock {

    private static class Sync extends AbstractQueuedSynchronizer {
        //是否處於佔用狀態
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        //當狀態為0時獲取鎖
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        //釋放鎖,將當前狀態設定為0
        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;

        }

        //返回一個condition,每個condition中都包含了一個condition佇列
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    //僅需要將操作代理到Sync上即可
    private Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);//呼叫tryAccquire
    }

    //當前已獲取鎖的執行緒響應中斷,釋放鎖,丟擲異常,並返回
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock() {
        return sync.tryAcquire(1);//嘗試立即獲取鎖
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));//嘗試超時獲取鎖
    }

    public void unlock() {
        sync.release(1);//釋放鎖
    }

    public Condition newCondition() {
        return sync.newCondition();
    }
}

總結-實現同步元件的方法

1. 獨佔鎖Mutex 是一個自定義同步元件,它允許同一時刻只允許同一個執行緒佔有鎖。
2.Mutex中定義了一個私有靜態內部類,該類繼承了同步器並實現了獨佔式獲取和釋放同步狀態。
3.在tryAcquire(int acquires)方法中,經過CAS設定成功(同步狀態設定為1),則
代表獲取了同步狀態,而在tryRelease(int releases) 方法中只是將同步狀態重
置為0。

3 佇列同步器的實現分析

3.1 同步佇列資料結構

  • 同步器依賴內部的同步佇列,即一個FIFO的佇列,這個佇列由雙向連結串列實現。節點資料從 佇列尾部插入,頭部刪除
  • node 資料結構
   struct node {
        node prev; //節點前驅節點
        node next; //節點後繼節點
        Thread thread; //獲取同步狀態的執行緒
        int waitStatus;  //等待狀態
        Node nextWaiter; //等待佇列中的後繼節點
   }

等待佇列 後續篇章介紹到condition會有相關記錄。

2836699-94c02dc951296ecc.png 同步佇列基本結構

3.2 無法獲取到同步狀態的執行緒節點被加入到同步佇列的尾部

本質上是採用 compareAndSetTail(Node expect,Node update),當一個執行緒成功的獲取了同步狀態
(或者鎖),其他執行緒將無法獲取到同步狀態,轉而被構造成為節點並加入到同步佇列中,而這個加入佇列的過程
必須要保證執行緒安全。所以採用了基於CAS的方式來設定尾節點的方法。
,需要傳遞當前節點認為的尾節點和當前節點,只有設定成功後,當前節點才正式與之前的尾節點建立關聯。

3.3 成功獲取同步狀態

同步佇列遵循FIFO,首節點是獲取同步狀態成功的節點,首節點的執行緒在釋放
同步狀態時,會喚醒後繼節點,而後繼節點將會在獲取同步狀態成功時,將自己設定為首節點。

3.4 獨佔式同步狀態獲取與釋放

  • 前驅節點為頭節點且能夠獲取同步狀態的判斷條件和執行緒進入同步佇列 來獲
    取同步狀態是自旋的過程。
  • 設定首節點是通過獲取同步狀態成功的執行緒來完成的acquireQueued(node,args)完成的

獨佔式獲取同步狀態的流程圖

2836699-c96354b1e0c36fec.png 獨佔式同步狀態(鎖)獲取流程