java獨佔鎖ReenTrantLock的實現

在jdk1.5之後,新增了Lock接口以及ReenTrantLock的實現類來代替同步原語sychronized,相較於sychronized同步原語,Lock接口的實現提供了:

  1. 獲取鎖的可操作性
  2. 嘗試非阻塞的獲取鎖
  3. 可中斷的獲取鎖
  4. 超時獲取鎖

以上的這些功能,是sychronized同步原語所不具有的功能,在需要使用高級特性的鎖時,就需要使用jdk提供的lock接口來實現。

一:同步器AQS
java AQS類結構的實現是使用模板方法設計模式來實現的,在頂級抽象類AQS中,定義了若干同步狀態獲取和釋放的方法,如下:

  1. 模板方法

    獨佔鎖的獲取和釋放同步狀態方法
    
public final void acquire(int arg);
public final void acquireInterruptibly(int arg)  throws InterruptedException ;
public final boolean tryAcquireNanos(int arg, long nanosTimeout)throws InterruptedException;
public final boolean release(int arg);
   共享鎖的獲取和釋放同步方法
public final void acquireShared(int arg);
public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException;
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException;
public final boolean releaseShared(int arg);
    獲取同步隊列的方法
public final Collection<Thread> getQueuedThreads();
public final int getQueueLength();
public final boolean hasQueuedThreads();
.........
  1. 子類重寫方法

    獨佔鎖

     protected boolean tryAcquire(int arg);
     protected boolean tryRelease(int arg) ;
    共享鎖
    protected int tryAcquireShared(int arg);
    protected boolean tryReleaseShared(int arg);
    同步器是否被當前線程所佔有
protected boolean isHeldExclusively();

那麼使用該同步器AQS,就可以同時實現獨佔鎖和共享鎖(通過繼承AQS並重寫對應的獲取和釋放同步狀態的方法)。

在AQS中,使用一個FIFO雙向隊列來存放當前線程,隊列的類結構爲:

    static final class Node {
        static final Node SHARED = new Node();
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        final boolean isShared() {
            return nextWaiter == SHARED;
        }
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    // Used to establish initial head or SHARED marker
        }

        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

在該節點類中,
prev和next爲當前節點的前驅和後繼結點的引用。
thread爲當前線程的引用。
waitStatus爲當前節點中線程的等待狀態,

 - 0 當一個節點初始化時爲
 - -1 表示後繼結點線程處於等待(被阻塞),若當前節點的線程釋放了同步狀態或者被取消,將會通知後繼結點的線程,使後繼結點得以運行(在獲取鎖和釋放鎖的時候非常重要)。
 - 1 表示處於同步隊列中的等待線程被取消或者被中斷,需要從同步隊列中取消等待狀態。
 - 還有幾個狀態後續分析

二:ReenTrantLock同步工具類的實現
該同步工具類通過聚合一個隊列同步器AQS的子類,來向上層lock的實現提供:

  1. 同步狀態管理 通過一個整型的變量private volatile int state;
  2. 線程的同步 通過一個FIFO的雙向隊列(使用CAS保證在隊列尾部線程安全的添加節點)
  3. 等待與喚醒線程 通過使用 LockSupport組件提供的阻塞線程功能來阻塞與喚醒節點中的線程

ReenTrantLock類聚合一個繼承自AQS同步器的抽象子類
abstract static class Sync extends AbstractQueuedSynchronizer,並使用兩個子類來實現該Sync類:

  • static final class NonfairSync extends Sync 非公平鎖,ReentrantLock類使用的默認AQS實現
  • static final class FairSync extends Sync 公平鎖

三:ReentrantLock的lock與unlock分析

假設有三個線程A,B,C,D同時請求獲得鎖資源:

當ReentrantLock類的lock方法被調用時

  public void lock() {
        sync.lock();
    }

會調用AQS的模板方法lock(),繼而會調用ReentrantLock默認實現的非公平鎖實現方式,調用

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

1,在該方法中,若:

  • A線程首先請求獲得該同步狀態,那麼就會以CAS的方式,原子更新同步狀態爲1,並獲得該同步狀態。
  • B線程,C線程,D線程在請求獲得鎖的時候,就會調用AQS的模板方法acquire(1)方法。
 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

2,此時B,C,D線程進入acquire方法之後,首先調用tryAcquire(1)方法,該方法爲ReenTrantLock類中同步器AQS的子類重寫方法(非公平鎖):

 final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

3,接着就會調用AQS的私有方法addWaiter(Node.EXCLUSIVE)創建獨佔式的同步隊列節點:

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {//1
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

private Node enq(final Node node) {
        for (;;) {//2
            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;
                }
            }
        }
    }

若假設請求順序爲B線程先,C,D線程後
- 首先根據當前線程創建同步節點,且waitStatus同步等待狀態初始化爲0。
- 當創建B線程對應的節點之後,嘗試快速添加失敗,會調用enq(node)方法。 在該方法中,設置了一個自旋式的CAS更新方式將AQS的同步隊列的尾節點引用tail指向新添加的節點。在第一次自旋中,會初始化一個空節點爲首節點,將head和tail都指向該首節點。
在首節點初始化之後,1處的CAS原子更新尾節點和2處的自旋式原子更新尾節點會保證將節點線程安全的添加到同步隊列。此時同步隊列如下:
這裏寫圖片描述

線程B,C,D均被放入同步節點被添加到AQS的同步隊列中,並且AQS的head引用指向一個空節點,節點的同步等待狀態waitStatus=0。
4,對於每個線程以及其對應的節點,接下來會被調用acquireQueued(addWaiter(Node.EXCLUSIVE), 1)方法,該方法會將當前節點自旋兩次(若非首節點),第一次將前驅節點的waitStatus更改爲-1,第二次使當前線程等待:

 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                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);
        }
    }
  • 對於D節點,首先獲取前驅節點C,在第一次自旋的時候,由於非首節點,並且獲取同步狀態失敗,會運行shouldParkAfterFailedAcquire(p, node)方法
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

在該方法中,由於前驅節點的waitStatus=0,故會將前驅節點的waitStatus以CAS的方式更新爲-1,表示若前驅節點釋放同步狀態或者被取消,通知並喚醒後續節點,返回false,第一次循環結束。
第二次循環:同樣非首節點,並且獲取同步狀態失敗,接着運行shouldParkAfterFailedAcquire(p, node)方法,第二次運行該方法時,獲取前驅節點waitStatus=-1,直接返回true。繼而運行 parkAndCheckInterrupt()方法:

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

該方法中,首先阻塞當前線程,然後判斷中斷標誌位。至此,D線程被阻塞,等待前驅節點中的線程通知,將其喚醒。
同理B,C線程會經過同樣的過程。且B,C線程都被阻塞

AQS同步器的同步隊列狀態爲:
`這裏寫圖片描述


若此時A線程釋放持有的同步狀態資源,在A線程中調用

 public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

釋放同步資源,那麼同步狀態state在tryRelease(arg)方法中被更新爲0,該方法爲AQS同步器子類重寫的方法

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

接着獲取首節點head,並且滿足if判斷(首節點爲非空節點,並且首節點的同步狀態被後繼結點在第一次自旋時更新爲-1),進入 unparkSuccessor(h)方法

private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

對於首節點 head,其ws=-1,接着獲取後繼結點B,將節點B中的線程B喚醒。至此,unlock()方法的兩個步驟結束(釋放同步狀態,喚醒首節點的後繼結點)。
節點B中的線程B被喚醒之後接着運行 final boolean acquireQueued(final Node node, int arg) 方法,並滿足if判斷條件,繼而執行

                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;

將節點B置爲首節點,並從acquireQueued()自旋方法中return。

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