JDK源碼——java.util.concurrent(三)

測試代碼:
https://github.com/kevindai007/springboot_houseSearch/tree/master/src/test/java/com/kevindai/juc

Condition

首先來看下Condition的簡單用法

public class ConditionTest {
    public static void main(String[] args) {
        final ReentrantLock reentrantLock = new ReentrantLock();
        final Condition condition = reentrantLock.newCondition();

        new Thread(new Runnable() {
            @Override
            public void run() {

                reentrantLock.lock();
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("線程1開始運行,等待條件執行");
                try {
                    condition.await();//與wait()一樣,會釋放鎖
                    System.out.println("線程1恢復執行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("線程1釋放鎖");
                    reentrantLock.unlock();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    reentrantLock.lock();
                    condition.signal();
                    System.out.println("線程2開始運行,釋放條件");
                }finally {
                    System.out.println("線程2釋放鎖");
                    reentrantLock.unlock();
                }
            }
        }).start();

    }
}

由這個例子能看出,Condition的await()方法與Object.wait()方法類似,signal()與Object.notify()類似,其實Condition還有signalAll()方法與Object.notifyAll()類似(用法類似,使用時需要獲取鎖,調用await()時會釋放鎖),下面咱們一起看看Condition的源碼
Condition是一個接口,主要方法如下

void await() throws InterruptedException;  

void awaitUninterruptibly();  

long awaitNanos(long nanosTimeout) throws InterruptedException;  

boolean await(long time, TimeUnit unit) throws InterruptedException;  

boolean awaitUntil(Date deadline) throws InterruptedException;  

void signal();  

void signalAll();

這些方法從字面上很容易理解,在此不做分析,下面咱們看看其實現類,先看await()方法

public final void await() throws InterruptedException {
            if (Thread.interrupted())//判斷當前線程是否被中斷
                throw new InterruptedException();
            //將當前線程作爲內容構造的節點node放入到條件隊列中並返回此節點
            Node node = addConditionWaiter();
            //釋放當前線程所擁有的鎖,返回值爲AQS的狀態位(即此時有幾個線程擁有鎖(考慮ReentrantLock的重入))
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            /*
                檢測此節點是否在同步隊列上,如果不在,說明此線程還沒有資格競爭鎖,此線程就繼續掛起;直到檢測到此節點在同步隊列上(在什麼時候加入的呢?在有線程發出signal信號的時候),
            */
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //此線程嘗試的獲取鎖
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            //清理下條件隊列中的不是在等待條件的節點
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            //報告異常
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            /*
                CONDITION,值爲-2,表示當前節點在等待condition,也就是在condition隊列中;如果此節點的狀態不是CONDITION,則需要將此節點在條件隊列中移除
            */
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;//獲取最後一個在等待的節點
            }
            //將此線程作爲內容構造一個節點加入到條件隊列末尾。
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
final int fullyRelease(Node node) {
            boolean failed = true;
            try {
                int savedState = getState();
                if (release(savedState)) {//釋放鎖
                    failed = false;
                    return savedState;
                } else {
                    throw new IllegalMonitorStateException();
                }
            } finally {
                if (failed)
                    node.waitStatus = Node.CANCELLED;
            }
        }

await方法的大概思想爲:首先將此代表該當前線程的節點加入到條件隊列中去,然後釋放該線程所有的鎖並開始睡眠,最後不停的檢測AQS隊列中是否出現了此線程節點.如果收到signal信號之後就會在AQS隊列中檢測到,檢測到之後,說明此線程又參與了競爭鎖.

注意:這裏提到了兩個隊列,一個是Condition中的隊列,一個是AQS的隊列;
AQS隊列是當前等待資源(這裏的資源就是鎖)的隊列,AQS會在資源被釋放後,依次喚醒隊列中從前到後的所有節點,使他們對應的線程恢復執行,直到隊列爲空.
Condition隊列的作用是維護一個等待signal信號的隊列,兩個隊列的作用是不同,事實上,每個線程也僅僅會同時存在以上兩個隊列中的一個,流程是這樣的:

用上面的Demo的兩個線程來描述
1、首先,線程1調用lock.lock()時,由於此時鎖並沒有被其它線程佔用,因此線程1直接獲得鎖並不會進入AQS同步隊列中進行等待。
2、在線程1執行期間,線程2調用lock.lock()時由於鎖已經被線程1佔用,因此,線程2進入AQS同步隊列中進行等待。
3、在線程1中執行condition.await()方法後,線程1釋放鎖並進入條件隊列Condition中等待signal信號的到來。
4、線程2,因爲線程1釋放鎖的關係,會喚醒AQS隊列中的頭結點,所以線程2會獲取到鎖。
5、線程2調用signal方法,這個時候Condition的等待隊列中只有線程1一個節點,於是它被取出來,並被加入到AQS的等待隊列中。注意,這個時候,線程1 並沒有被喚醒。只是加入到了AQS等待隊列中去了
6、待線程2執行完成之後並調用lock.unlock()釋放鎖之後,會喚醒此時在AQS隊列中的頭結點.所以線程1開始爭奪鎖(由於此時只有線程1在AQS隊列中,因此沒人與其爭奪),如果獲得鎖繼續執行。
7、直到線程1釋放鎖整個過程執行完畢。

可以看到,整個協作過程是靠結點在AQS的等待隊列和Condition的等待隊列中來回移動實現的,Condition作爲一個條件類,自己維護了一個等待信號的隊列,並在適時的時候將結點加入到AQS的等待隊列中來實現的喚醒操作。
下面分析一下signal()方法

        public final void signal() {
            //檢測當前線程是否爲擁有鎖的獨佔線程
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            /*
              firstWaiter爲condition自己維護的一個鏈表的頭結點,
              取出第一個節點後開始喚醒操作
            */

            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
        private void doSignal(Node first) {
            do {
                //修改頭結點,完成舊頭結點的移出工作
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

doSignal()方法幹了兩件事:1、修改條件隊列中的頭結點,1、完成舊的頭結點的移出工作,即從Condition隊列中移出到AQS同步隊列中去。

    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
         //如果不能改變等待狀態,那麼當前節點被取消,return false
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
         //將節點加入到syn隊列中去,返回的是syn隊列中node節點前面的一個節點
        Node p = enq(node);
        int ws = p.waitStatus;
        //將節點加入到syn隊列中去,返回的是syn隊列中node節點前面的一個節點
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

可以看到,正常情況 ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL) 這個判斷是不會爲true的,所以,不會在這個時候喚醒該線程.
只有到發送signal信號的線程調用reentrantLock.unlock()後,它已經被加到AQS的等待隊列中,纔可能會被喚醒.

ReentrantLock

ReentrantLock可重入鎖,與synchronized類似,但更方便靈活,可作爲替代使用:
1.支持公平/非公平鎖
2.支持響應超時,響應中斷
3.支持condition

ReentrantLock實現了Lock接口,內部繼承AQS實現這些功能,使用AQS的state來表示鎖的重入次數,lock接口如下
這裏寫圖片描述
看下如何實現AQS的

abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        abstract void lock();

        //非公平鎖tryAcquire
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //判斷state是否被佔用 
            if (c == 0) {//未被佔用則cas佔用,並設置當前線程爲佔用線程
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//如果被佔用,則判斷是否是當前線程佔用的(鎖可重入),如果是就把state+1
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
        //釋放資源
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())//如果當前線程不是佔用線程,則報錯
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {//狀態-1後如果資源未被佔用,則沒有佔用線程
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
        //當前線程是否是佔用線程
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
        //獲取Codition,conditionObject維護一個條件隊列,見上篇
        final ConditionObject newCondition() {
            return new ConditionObject();
        }
        //獲取佔用線程
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked() {
            return getState() != 0;
        }

        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

ReentrantLock的實現又分爲兩種,公平鎖、非公平鎖,咱們直接看非公平鎖(公平鎖前面分析AQS的時候分析過了,http://blog.csdn.net/kevindai007/article/details/70314814):

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        final void lock() {
            //直接嘗試獲取資源(不管有沒有線程在條件隊列中等待)
            if (compareAndSetState(0, 1))

                //獲取成功則把當前線程設置爲佔有線程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //如果未成功則加入等待隊列(加入等待隊列的線程是依次被喚醒的)
                acquire(1);
        }

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

ReentrantLock釋放鎖的過程在AQS的分析中都已經分析過了,在此不做重複分析.

ReetrantLock的公平和非公平的區分就是在Acquire的時候,非公平會先直接嘗試cas修改,不成功再去排隊;而公平鎖就是老老實實請求排隊操作。

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