[Java源碼][并發(fā)J.U.C]---用代碼一步步實現(xiàn)AQS(1)---獨占鎖的獲取和釋放

前言

對源碼AQS(AbstractQueuedSynchronizer)(在java.util.concurrent.locks)的分析會是一個系列文章.這篇文章主要是以例子的形式并且一步步把源碼中的代碼加入我們自己的代碼中,從而一步步完成對AQS的源碼分析.

完整代碼:代碼

添加必要的接口和抽象類

Lock

public interface Lock {
    // 獲取鎖
    void lock();
    // 獲取鎖 響應(yīng)中斷
    void lockInterruptibly() throws InterruptedException;
    // 獲取鎖 如果鎖是available立即返回true, 如果鎖不存在就立即返回false
    boolean tryLock();
    // 在上面的基礎(chǔ)上加一個時間限制
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    // 釋放鎖
    void unlock();
    // 后續(xù)博客會討論
    Condition newCondition();
    
}

定義了鎖的一些常規(guī)操作

AbstractOwnableSynchronizer

定義了一個獨占鎖當前所對應(yīng)的線程并且提供了setget方法

public class AbstractOwnableSynchronizer implements java.io.Serializable {
    
    private static final long serialVersionUID = 3737899427754241961L;
    
    protected AbstractOwnableSynchronizer(){}
    //獨占鎖對應(yīng)的線程
    private transient Thread exclusiveOwnerThread;
    
    protected final void setExclusiveOwnerThread(Thread thread) {
        this.exclusiveOwnerThread = thread;
    }
    
    protected final Thread getExclusiveOwnerThread() {
        return this.exclusiveOwnerThread;
    }
}

實現(xiàn)AbstractOwnableSynchronizer(以下以AQS簡稱)

在介紹主體方法之前先介紹一個內(nèi)部類Node,AQS中等待隊列的元素就是Node類的一個個對象.

Node

static final class Node {
        // 共享
        static final Node SHARED = new Node();
        // 表示獨占
        static final Node EXCLUSIVE = null;
        // 表明這個節(jié)點已經(jīng)被取消
        static final int CANCELLED =  1;
        // 表明需要喚醒下一個等待的線程
        static final int SIGNAL    = -1;
        // 后續(xù)會討論
        static final int CONDITION = -2;
        // 后續(xù)會討論
        static final int PROPAGATE = -3;
        // 當前節(jié)點的等待狀態(tài)
        volatile int waitStatus;
        // 當前節(jié)點的前一個節(jié)點
        volatile Node prev;
        // 當前節(jié)點的下一個節(jié)點
        volatile Node next;
        // 當前節(jié)點所表示的線程
        volatile Thread thread;
        // 用于判斷是否是獨占還是共享
        Node nextWaiter;
        
        //判斷是獨占還是共享
        final boolean isShared() {
            return nextWaiter == SHARED;
        }
        // 取前一個節(jié)點
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
        // 無參構(gòu)造函數(shù)
        Node() {}
        
        Node(Thread thread, Node mode) {   
            this.nextWaiter = mode;
            this.thread = thread;
        }
        
        Node(Thread thread, int waitStatus) {
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
        
        public String toString() {
            return "[" + (thread == null ? "NULL" : thread.getName()) + "," + waitStatus + "]";
        }
    }

Node類是一個封裝了thread并且?guī)в幸恍顟B(tài)的節(jié)點
1. 所以它有個thread屬性表示該節(jié)點所對應(yīng)的線程.
2. 還有屬性nextWaiter來表示該節(jié)點是共享鎖SHARED的還是獨占鎖EXCLUSIVE
3. toString()函數(shù)是為了方便打印我自己加上的
4. 有一個waitStatus來表示當前節(jié)點的等待狀態(tài),有(CANCELLED,SIGNAL,CONDITION,PROPAGATE)

CANCELLED: 表示該節(jié)點已經(jīng)被取消了
SIGNAL: 表示該節(jié)點在release或者取消時需要去喚醒下一個線程(這里留個疑問?還有在什么情況下會去喚醒下一個節(jié)點的線程)
后面的兩個屬性會在后面的系列博客中有分析到
其實還有一個屬性是0在生成節(jié)點的時候waitStatus默認是0

AQS的屬性

public class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer 
            implements java.io.Serializable {
    
    private static final long serialVersionUID = 7373984972572414691L;
    
    protected AbstractQueuedSynchronizer() { }

    static final class Node {}

    // 同步器等待隊列的頭節(jié)點
    private transient volatile Node head;
    // 同步器等待隊列的尾節(jié)點
    private transient volatile Node tail;
    // 同步器的狀態(tài)值
    private volatile int state;
    // 獲取同步器狀態(tài)值
    protected final int getState() {
        return state;
    }
    // 設(shè)置同步器的狀態(tài)值
    protected final void setState(int newState) {
        state = newState;
    }

    public final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

    private static final Unsafe unsafe;
    private static final long stateOffset;
    private static final long headOffset;
    private static final long tailOffset;
    private static final long waitStatusOffset;
    private static final long nextOffset;

    static {
        try {
            
                Field f = Unsafe.class.getDeclaredField("theUnsafe");
                f.setAccessible(true);
                unsafe = (Unsafe)f.get(null);
                
            stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
            tailOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
            waitStatusOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("waitStatus"));
            nextOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("next"));

        } catch (Exception ex) { throw new Error(ex); }
    }

    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

    private static final boolean compareAndSetWaitStatus(Node node,
                                                         int expect,
                                                         int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset,
                                        expect, update);
    }

    private static final boolean compareAndSetNext(Node node,
                                                   Node expect,
                                                   Node update) {
        return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
    }

先說幾個屬性
1. headtail表示等待隊列的頭尾節(jié)點
2. state 表示AQS的狀態(tài)值,在后面的例子中可以更清晰的理解它的作用. 并且提供了setget方法.

然后說一下跟源碼的區(qū)別
源碼:private static final Unsafe unsafe = Unsafe.getUnsafe();
由于在我們的代碼使用這個語句會報錯,因為在類加載的時候必須要使用對應(yīng)的類加載器,然而我們可以使用反射的方式拿到該對象.

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe)f.get(null);

Unsafe的相關(guān)說明會寫一個博客來另外分析,現(xiàn)在只需要知道它在多線程情況下是可以安全實現(xiàn)那個對應(yīng)的操作就可以了

接下來我們使用一個例子來看看如何一步步實現(xiàn)一個互斥鎖的.

例子Mutex互斥鎖

既然我們是要實現(xiàn)一個鎖,那我們可以繼承Lock接口,然后我們使用AQS的一個子類的一個對象來真正實現(xiàn)Lock的功能.

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

public class Mutex implements Lock {
    
    private final Sync sync = new Sync();
    
    private static class Sync extends AbstractQueuedSynchronizer {
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
        public boolean tryAcquire(int acquires) {
            if (super.compareAndSetState(0, 1)) {
                super.setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
    }
    
    public void printWaitingNode() {
        sync.printQueue();
    }

    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        // TODO Auto-generated method stub
        
    }

    @Override
    public boolean tryLock() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void unlock() {
        // TODO Auto-generated method stub
        
    }

    @Override
    public Condition newCondition() {
        // TODO Auto-generated method stub
        return null;
    }
}1

從上面代碼中可以看到,AQS中的狀態(tài)是0時表明鎖是空閑的,1表明鎖已經(jīng)被某個線程獨占了,表明這個線程已經(jīng)擁有了這個鎖,那根據(jù)我們的猜想,其余沒有獲得鎖的線程放到哪里呢?沒錯,就是放到等待隊列中,那接下來我們先看看用一個測試類看看效果.

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Mutex m = new Mutex();
        for (int i = 0; i < 5; i++) {
            new Thread(new Runner(m), "thread-" + i).start();;
        }
        for (int i = 0; i < 5; i++) {
            m.printWaitingNode();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    static class Runner implements Runnable {
        Mutex m;
        public Runner(Mutex m) {
            this.m = m;
        }
        @Override
        public void run() {
            m.lock();
            System.out.println(Thread.currentThread().getName() + " runs");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            m.unlock();
        }
    }
}

運行結(jié)果如下:
表明thread-0獲得了鎖,其余的線程在等待隊列中.

thread-0 runs
[NULL,-1]->[thread-1,-1]->[thread-2,-1]->[thread-3,-1]->[thread-4,0]->

那接下來看看如何一步步實現(xiàn)AQS中的acquire方法的.建議看下面分析是可以帶著Mutex的代碼來看下面的分析

acquire方法

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

由于調(diào)用了tryAcquireacquireQueued方法,我們先看看這兩個方法再回頭來分析這個方法

tryAcquire方法

protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
}

作用: 獲得了鎖返回true,沒有獲得返回false
這個方法是為子類準備的,所以在Mutex也實現(xiàn)了tryAcquire方法,所以第一個線程thread-0獲得鎖的調(diào)用過程中, tryAcquire已經(jīng)返回true,所以根本不會去執(zhí)行acquireQueued方法和selfInterrupt

所以當該線程沒有獲得鎖的時候就要執(zhí)行acquireQueued方法,可以大膽預(yù)測該方法是想讓這個線程加入到等待隊列中并且讓它阻塞(其實仔細一想,獲得了鎖的線程其實是程序沒有去阻塞它而已嘛,讓它繼續(xù)執(zhí)行罷了,那沒有獲得鎖的線程就得阻塞它,讓它沒有辦法繼續(xù)執(zhí)行)

在看acquireQueued方法前,先看看addWaiter方法

addWaiter(Node.EXCLUSIVE)

注意添加的參數(shù)是獨占型的節(jié)點

//添加一個節(jié)點到當前隊列 在尾部添加
    private Node addWaiter(Node mode) {
        // 生成一個mode類型的節(jié)點 節(jié)點的thread是當前線程
        Node node = new Node(Thread.currentThread(), mode);
        /**
         * 如果等待隊列尾部不為空,則直接通過unsafe操作放到隊列尾部
         * 如果不是則調(diào)用 enq方法把節(jié)點插入到等待隊列中.
         */
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

作用:將當前thread包裝成節(jié)點,獨占型,waitStatus=0
流程分四步:
1. 生成node節(jié)點[獨占型,waitStatus=0,thread=Thread.currentThread()]
2. 如果等待隊列尾部存在,表明鏈表已經(jīng)初始化過了,可以直接放到鏈表尾部
3. 如果鏈表沒有初始化,那就是讓enq(node)去初始化鏈表并且把node加入到鏈表尾部
4. 返回這個node

接下來看看enq方法

enq(final Node node)方法

參數(shù)node是即將要放入到隊列尾部的節(jié)點

// 將node節(jié)點放到等待隊列的尾部
    private Node enq(final Node node) {
        /**
         * 無限循環(huán)到條件滿足后退出
         */
        for (;;) {
            Node t = tail;
            /**
             * 如果隊列尾部是空 表明隊列還沒有進行過初始化
             * 如果不為空 則把節(jié)點鏈接到隊列的尾部
             */
            if (t == null) {
                    //生成一個thread為null的節(jié)點并且設(shè)置為頭節(jié)點
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                    //利用unsafe的操作把當前節(jié)點設(shè)置到尾部
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

作用:將node節(jié)點放到等待隊列的尾部
流程:通過for循環(huán)自旋的方式一直檢查尾部,如果尾部為空,則創(chuàng)建一個thread為null的頭節(jié)點,然后將其設(shè)置為尾節(jié)點,如果不為空,則把該節(jié)點加入到隊列尾部.
問題:至于為什么需要用for循環(huán)自旋的方式呢?

1. 當鏈表是空的時候,只有一個線程進來的時候會先進入if(t==null)塊中,完成后會再次進入for循環(huán)中的else塊中并返回.
2. 當有兩個線程同時進入else塊中的時候,只能有一個線程會設(shè)置成功,所以失敗的那個線程會進入for循環(huán)后再次進入else塊中.

到這里我們先簡單總結(jié)一下:

1. 該線程由于在tryAcquire方法中返回false表明沒有獲得鎖,因此需要被阻塞
2. 我們已經(jīng)通過addWaiter方法把該線程包裝成一個獨占型的節(jié)點放入到等待隊列的尾部并且返回了該節(jié)點.

那按照思路接下來的操作就應(yīng)該是如何來阻塞這個線程了,那接下來我們來看看這里最核心的acquireQueued方法

acquireQueued(final Node node, int arg)方法

/**
     * 
     * @param node 當前節(jié)點
     * @param arg acquire請求的狀態(tài)參數(shù)
     * @return 返回是否需要對當前運行線程進行中斷
     */
    
    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);
        }
    }

這里有幾個新的方法,我先簡單介紹一下每個方法的作用,具體的細節(jié)分析會放到后面的分析中.
1. predecessor()取得當前節(jié)點node的前驅(qū)節(jié)點 屬于Node中的成員方法
2.setHead(node)把當前節(jié)點node設(shè)置為頭節(jié)點
3. shouldParkAfterFailedAcquire(p, node) 返回當前節(jié)點node是否可以進行休眠
4. parkAndCheckInterrupt()讓當前線程進行休眠狀態(tài)并且在喚醒或者中斷后返回中斷狀態(tài)
5. cancelAcquire(node) 取消當前節(jié)點node

關(guān)于兩個屬性也簡單介紹一下
1. failed表示獲得鎖是否成功
2. interrupted 表示當前節(jié)點對應(yīng)的線程是否有被中斷過

作用:利用for循環(huán)自旋的方式讓等待獲取鎖的線程都進行休眠,因為休眠可以節(jié)約cpu資源,讓擁有了鎖的線程可以充分利用cpu運行

流程如下:

所以我們接下來就看看上面那三個方法的具體實現(xiàn)

setHead(node)方法
/**
     * 作用: 把node節(jié)點設(shè)置為等待隊列的頭節(jié)點
     */
    private void setHead(Node node) {
        /**
         * 1. 把當前節(jié)點設(shè)置為頭節(jié)點
         * 2. 把當前節(jié)點的thread設(shè)置為null,因為這個node.thread已經(jīng)獲得了鎖
         * 3. 把當前節(jié)點的前鏈接設(shè)置為null
         */
        head = node;
        node.thread = null;
        node.prev = null;
    }

作用: 把節(jié)點設(shè)置為等待隊列的頭節(jié)點
注意: waitStatus的值沒有修改

shouldParkAfterFailedAcquire(Node pred, Node node)方法

/**
     * 這個方法必須要保證pred == node.prev
     * 作用: 此方法是在判斷node節(jié)點是否可以進行休眠狀態(tài), 因為進行休眠狀態(tài)可以節(jié)約資源利用(cpu)
     *      所以需要讓node休眠前要確保有節(jié)點可以喚醒它,因此下面所做的事情就是檢查等待隊列中
     *      前面的節(jié)點是否滿足條件.
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /**
             * 如果節(jié)點pred的狀態(tài)是Node.SIGNAL的時候,表明pred在release的時候
             * 會喚醒下一個節(jié)點,也就是node節(jié)點
             * 返回true 表明node節(jié)點可以休眠
             */
            return true;
        if (ws > 0) {
            /**
             * 表明節(jié)點pred所對應(yīng)的線程已經(jīng)被取消了,因此需要一直往前找
             * 直到前一個節(jié)點的waitStatus小于等于0,中間的那些取消的節(jié)點
             * 會被刪除 因為最后pred.next = node.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /**
             * 所以這個分支表示當前節(jié)點的等待狀態(tài)要么是0或者PROPAGATE,
             * 此時直接把它設(shè)置為SIGNAL
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

作用:是判斷node節(jié)點是否可以進行休眠狀態(tài),但是休眠前需要保證有節(jié)點可以喚醒它,這個時候Node.SINGAL就是這個作用.

流程:

juc_3.png

ws>0塊所做的內(nèi)容如下:

juc_2(1).png

[疑問]:多個線程進入到這里的時候會不會有問題?歡迎大家一起討論

看完上面兩個圖然后再結(jié)合著acquireQueued方法看一下可以知道線程在休眠前必須要保證有前驅(qū)節(jié)點可以喚醒它,而這個標志就是使得前驅(qū)節(jié)點的waitStatus要為Node.SIGNAL

請注意:比如pred的waitStatus不是1或者-1的時候,當?shù)谝淮握{(diào)用該方法時會返回false,第二次調(diào)用會直接返回true,因為返回到acquireQueued中的for循環(huán)中還是有可能會繼續(xù)調(diào)用該方法.(waitStatus是1的時候大家可以自行判斷一下)

那到這里我們已經(jīng)知道當線程沒有取得鎖的時候,已經(jīng)有個方法會告訴當前線程是否可以進行休眠,并且在沒有獲得鎖的情況下盡量會讓該線程休眠,所以接下來我們看看如何讓當前線程休眠的

parkAndCheckInterrupt()

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

作用: 讓當前線程進行休眠狀態(tài)并且在喚醒或者中斷后返回中斷狀態(tài)
注意: 第一句就會讓當前線程進行休眠狀態(tài),只有等到當前線程被喚醒后才可以進入第二句話

線程被喚醒有兩種情況:
1. 被前面的(SIGNAL)節(jié)點喚醒 此時第二句話返回false
2. 線程被中斷 此時第二句話返回true (關(guān)于中斷的話題會有另外專門博客介紹)

其實到這里我們就可以看明白我們前面那個例子最后打印出來的等待隊列是如何產(chǎn)生的了.

階段性總結(jié)

分析到這里我們就可以回頭看看acquire(int arg)的流程圖了

juc_5(1).png

另外對上面的例子所表現(xiàn)出來的結(jié)果如下:


juc_4.png

解釋一下:
1. thread-0沒有出現(xiàn): 因為thread-0已經(jīng)獲得了鎖在tryAcquire中就已經(jīng)返回了,可以看流程圖
2. 頭節(jié)點的等待狀態(tài)是-1: 這是因為在鏈表加入thread-1那個節(jié)點的時候會通過shouldParkAfterFailedAcquire將剛剛初始化的0狀態(tài)改變成Node.SIGNAL狀態(tài).
3. 尾節(jié)點thread-4的等待狀態(tài)是0: 因為它就是初始化的狀態(tài),還沒有發(fā)生過改變.

接下來就是剩下的cancelAcquire

cancelAcquire(Node node)

/**
     * 添加cancelAcquire方法
     * 作用: 取消該節(jié)點
     */
    private void cancelAcquire(Node node) {
        // 如果節(jié)點為null 直接返回
        if (node == null)
            return;
        // 設(shè)置節(jié)點所對應(yīng)的thread為null
        node.thread = null;

        // 找到node的前驅(qū)節(jié)點(必須是沒有被取消的節(jié)點)
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        // 前驅(qū)節(jié)點的next節(jié)點
        Node predNext = pred.next;

        // 設(shè)置當前節(jié)點為取消狀態(tài)
        node.waitStatus = Node.CANCELLED;

        /**
         * 如果當前節(jié)點是尾節(jié)點: (表明當前節(jié)點可以直接取消,不用管后面的節(jié)點,因為后面沒有節(jié)點了)
         *       1. 設(shè)置前驅(qū)節(jié)點pred成為新的尾節(jié)點
         *       2. 在1成功的條件下設(shè)置尾節(jié)點的next為null
         */
        if (node == tail && compareAndSetTail(node, pred)) {
    //情況3
            compareAndSetNext(pred, predNext, null);
        } else {
                /**
                 * 表明node后面還有節(jié)點,因此后面的節(jié)點會需要喚醒
                 * 有兩種情況:
                 *     1. 前驅(qū)節(jié)點不是頭節(jié)點,并且前驅(qū)節(jié)點的等待狀態(tài)是SINGAL或者可以成為SINGAL狀態(tài),
                 *        并且前驅(qū)節(jié)點的thread不能為null. 這種情況下不需要去喚醒node后面的線程,因為pred節(jié)點可以去喚醒的
                 *     2. 如果不符合1,就需要主動去喚醒node后面的節(jié)點線程了,因為此時不喚醒,node后面節(jié)點的線程就沒辦法再喚醒了
                 */
            int ws;
            //情況1 
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                    //情況2
                unparkSuccessor(node);
            }
            //設(shè)置node節(jié)點自旋
            node.next = node; // help GC
        }
    }

當如果有異常的時候會進入該方法中取消該節(jié)點
以下列出了三種情況分別出現(xiàn)的情景:


juc_4(3).png

注意:最后將該節(jié)點設(shè)置為自旋方便GC操作

直接看```unparkSuccessor(node)方法

unparkSuccessor(Node node)

/**
     *  喚醒node的后驅(qū)節(jié)點(node后面第一個沒有被取消的節(jié)點)
     */
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * 取得node的后繼節(jié)點
         * 
         */
        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);
    }

作用:喚醒node的后驅(qū)節(jié)點(node后面第一個沒有被取消的節(jié)點)
注意: 在尋找node的后繼節(jié)點的時候,為什么需要從后往前尋找,情況上圖中的情況2

這里有兩點疑問我還沒有弄清楚,歡迎大家一起討論
1. 開始的部分為什么需要把ws設(shè)置為0,所以我把英文直接貼上來了

接下來再看一個selfInterrupt()方法

selfInterrupt()方法

static void selfInterrupt() {
        Thread.currentThread().interrupt();
}

讓當前線程中斷,線程中斷的話題會寫一個專門的博客來分析的

打印等待隊列的代碼

public void printQueue() {
    printQueueNode(tail);
    System.out.println();
}
    
public void printQueueNode(Node node) {
    if (node == null) return;
    printQueueNode(node.prev);
    System.out.print(node + "->");
}

這兩個方法是我自己為了方便打印加的,因為需要從鏈表的尾部遍歷,因此我就用了個遞歸的寫法.

至此關(guān)于獲取鎖這部分的代碼就已經(jīng)分析完了,你可以把代碼下載下來自己運行著看一下.

最后加一個鎖的釋放

鎖的釋放 release(int arg)方法

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

代碼和邏輯都比較簡單,就是當head不為null并且狀態(tài)值不為0的時候,會去喚醒下一個頭結(jié)點后的后驅(qū)節(jié)點.

請注意喚醒的線程會從parkAndCheckInterrupt中返回(此時返回值是false)到acquireQueued方法中去嘗試獲取鎖

所以接下來我們完善一下Mutex類和測試用例

完善Mutex類和測試用例

在Mutex類中unlock修改成

    @Override
    public void unlock() {
        // TODO Auto-generated method stub
        sync.release(1);
    }

在Sync類中添加tryRelease方法

public boolean tryRelease(int releases) {
            if (super.getState() == 0) 
                throw new IllegalMonitorStateException();
            super.setExclusiveOwnerThread(null);
            super.setState(0);
            return true;
        }

再次運行例子,在我的電腦上結(jié)果如下:

thread-0 runs
[NULL,-1]->[thread-1,-1]->[thread-2,-1]->[thread-3,-1]->
[NULL,-1]->[thread-1,-1]->[thread-2,-1]->[thread-3,-1]->[thread-4,0]->
thread-1 runs
[NULL,-1]->[thread-2,-1]->[thread-3,-1]->[thread-4,0]->
thread-2 runs
[NULL,-1]->[thread-3,-1]->[thread-4,0]->
thread-3 runs
[NULL,-1]->[thread-4,0]->
thread-4 runs

[疑問]: 為什么會出現(xiàn)

[NULL,-1]->[thread-1,-1]->[thread-2,-1]->[thread-3,-1]->

結(jié)尾

至此對獨占鎖的分析差不多就到這里了,歡迎大家對上面存在的疑問一起討論哈,或者哪里寫得有問題也歡迎提出一起討論哈.

后面的文章將會繼續(xù)接著本文繼續(xù)討論,對獨占鎖的中斷獲取等等,就是一步步把Mutex沒有實現(xiàn)的方法實現(xiàn)完.

完整代碼:代碼
另外如果有幫助到您一點點,請幫忙給個贊哈.謝謝觀看.
下一篇: [Java源碼][并發(fā)J.U.C]---用代碼一步步實現(xiàn)AQS(2)---獨占鎖

參考

1. Java并發(fā)編程的藝術(shù)
2. Java1.8 java.util.concurrent.locks包的源代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凤壁,一起剝皮案震驚了整個濱河市啄刹,隨后出現(xiàn)的幾起案子滔韵,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拼卵,死亡現(xiàn)場離奇詭異,居然都是意外死亡蛮艰,警方通過查閱死者的電腦和手機腋腮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來壤蚜,“玉大人即寡,你說我怎么就攤上這事⊥嗨ⅲ” “怎么了聪富?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長著蟹。 經(jīng)常有香客問我墩蔓,道長梢莽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任奸披,我火速辦了婚禮昏名,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘阵面。我一直安慰自己轻局,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布样刷。 她就那樣靜靜地躺著仑扑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪置鼻。 梳的紋絲不亂的頭發(fā)上夫壁,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音沃疮,去河邊找鬼盒让。 笑死,一個胖子當著我的面吹牛司蔬,可吹牛的內(nèi)容都是我干的邑茄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼俊啼,長吁一口氣:“原來是場噩夢啊……” “哼肺缕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起授帕,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤同木,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后跛十,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彤路,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年芥映,在試婚紗的時候發(fā)現(xiàn)自己被綠了洲尊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡奈偏,死狀恐怖坞嘀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情惊来,我是刑警寧澤丽涩,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站裁蚁,受9級特大地震影響矢渊,放射性物質(zhì)發(fā)生泄漏检眯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一昆淡、第九天 我趴在偏房一處隱蔽的房頂上張望锰瘸。 院中可真熱鬧,春花似錦昂灵、人聲如沸避凝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽管削。三九已至,卻和暖如春撑螺,著一層夾襖步出監(jiān)牢的瞬間含思,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工甘晤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留含潘,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓线婚,卻偏偏與公主長得像遏弱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子塞弊,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

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