Lock鎖詳解

目錄

Lock

ReentrantLock

ReentrantReadWriteLock

Lock和synchronized的簡單對比

​AQS(AbstractQueuedSynchronizer)

AQS的內部實現

CAS

ReentrantLock的實現原理

lock與unlock過程總結

公平鎖和非公平鎖的區別

Condition

LockSupport

總結


轉自:https://blog.csdn.net/u011212394/article/details/82314489

Lock

在jdk1.5以後,增加了juc併發包且提供了Lock接口用來實現鎖的功能,它除了提供了與synchroinzed關鍵字類似的同步功能,還提供了比synchronized更靈活api實現。

ReentrantLock

可重入鎖。如果當前線程t1通過調用lock方法獲取了鎖之後,再次調用lock,是不會再阻塞去獲取鎖的,直接增加重入次數就行了。與每次lock對應的是unlock,unlock會減少重入次數,重入次數減爲0纔會釋放鎖。

ReentrantReadWriteLock

  • 可重入讀寫鎖。讀寫鎖維護了一個讀鎖,一個寫鎖。
  • 讀鎖同一時刻允許多個讀線程訪問。
  • 寫鎖同一時刻只允許一個寫線程,其他讀/寫線程都需要阻塞。

Lock和synchronized的簡單對比

AQS(AbstractQueuedSynchronizer)

Lock之所以能實現線程安全的鎖,主要的核心是AQS,AQS提供了一個FIFO隊列,可以看做是一個用來實現鎖以及其他需要同步功能的框架。AQS的使用依靠繼承來完成,子類通過繼承自AQS並實現所需的方法來管理同步狀態。例如常見的ReentrantLock,CountDownLatch等。

從使用上來說,AQS的功能可以分爲兩種:獨佔和共享。

獨佔鎖模式下,每次只能有一個線程持有鎖,ReentrantLock就是以獨佔方式實現的互斥鎖。

共享鎖模式下,允許多個線程同時獲取鎖,併發訪問共享資源,比如ReentrantReadWriteLock。

AQS的內部實現

同步器依賴內部的同步隊列(一個FIFO雙向隊列)來完成同步狀態的管理,當前線程獲取同步狀態失敗時,同步器會將當前線程以及等待狀態等信息構造成爲一個節點(Node)並將其加入同步隊列,同時會阻塞當前線程,當同步狀態釋放時,會把首節點中的線程喚醒,使其再次嘗試獲取同步狀態。

Node的主要屬性:

static final class Node {
    //表示節點的狀態,包含SIGNAL、CANCELLED、CONDITION、PROPAGATE、INITIAL
    volatile int waitStatus;
    //前繼節點
    volatile Node prev;
    //後繼節點
    volatile Node next;
    //當前線程
    volatile Thread thread;
    //存儲在condition隊列中的後繼節點
    Node nextWaiter; 
}

waitStatus節點的幾種狀態: 

CANCELLED,值爲1,由於在同步隊列中等待的線程等待超時或者被中斷,需要從同步隊列中取消等待,節點進入該狀態將不會變化。

SIGNAL,值爲-1,後繼節點的線程處於等待狀態,而當前節點的線程如果釋放了同步狀態或者被取消,將會通知後繼節點,使後繼節點得以運行。

CONDITION,值爲-2,節點在等待隊列中,節點線程等待在Condition上,當其他線程對Condition調用了signal()方法後,該節點將會從等待隊列中轉移到同步隊列中,加入到對同步狀態的獲取中。

PROPAGATE,值爲-3,表示下一次共享式同步狀態獲取將會無條件地被傳播下去。

INITAL,值爲0,初始狀態。
設置尾節點:

當一個線程成功地獲取了同步狀態(或者鎖),其他線程將無法獲取,轉而被構造成爲節點並加入同步隊列,而這個過程必須保證線程安全,因此同步器提供了一個基於CAS的設置尾節點的方法:compareAndSetTail(Node expect,Nodeupdate),它需要傳遞當前線程“認爲”的尾節點和當前節點,只有設置成功後,當前節點才正式與之前的尾節點建立關聯。

/**
     * CAS tail field. Used only by enq.
     */
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

設置首節點:

同步隊列遵循FIFO,首節點是獲取同步狀態成功的節點,首節點的線程在釋放同步狀態時,將會喚醒後繼節點,而後繼節點將會在獲取同步狀態成功時將自己設置爲首節點。

設置首節點是通過獲取同步狀態成功的線程來完成的,由於只有一個線程能夠成功獲取到同步狀態,因此設置頭節點的方法並不需要使用CAS來保證,它只需要將首節點設置成爲原首節點的後繼節點並斷開原首節點的next引用即可。

 /**
     * CAS head field. Used only by enq.
     */
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

CAS

在AQS中,除了本身的鏈表結構以外,還有一個很關鍵的功能,就是CAS。這個功能可以保證線程在高併發的情況下能安全的加入到AQS隊列中。

在AQS設置首節點和尾節點的方法中,都用到了unsafe.compareAndSwapObject方法。Unsafe類是在sun.misc包下,不屬於Java標準,但是很多Java的基礎類庫,包括一些被廣泛使用的高性能開發庫都是基於Unsafe類開發的,比如Netty、Hadoop、Kafka等,Unsafe類可認爲是Java中留下的後門,提供了一些底層操作,如直接內存訪問、線程調度等。
compareAndSwapObject是一個native方法

    /**
     * var1:需要改變的對象
     * var2:偏移量
     * var4:期待值
     * var5:更新後的值
     */
    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

它的作用是,若當前時刻的值var1等於預期值var4,則更新爲期望值 var5,如果更新成功,則返回true,否則返回false。

ReentrantLock的實現原理

ReentrantLock - > lock()

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

這個是獲取鎖的入口。sync是一個實現了AQS的抽象類,這個類的主要作用是用來實現同步控制的,sync有兩個實現,一個是NonfairSync(非公平鎖)、另一個是FailSync(公平鎖)。

ReentrantLock - > NonfairSync.lock

    final void lock() {
        /*
          通過cas算法去改變state的值
          state=0表示無鎖狀態,state>0表示有鎖狀態
          這是跟公平鎖的主要區別,一上來就試探鎖是否空閒,如果可以插隊,則設置獲得鎖的線程爲當前線程
        */
        if (compareAndSetState(0, 1)) 
            //exclusiveOwnerThread屬性是AQS從父類AbstractOwnableSynchronizer中繼承的屬性,用來保存當前佔用鎖的線程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1); //嘗試去獲取鎖
    }

當鎖被佔用時,執行acquire(1),以下都是對這個方法的詳細分析

    /**
      * 嘗試獲取獨佔鎖,獲取成功則返回
      * 獲取失敗則繼續自旋獲取鎖,並且判斷中斷標識,如果中斷標識爲true,則設置線程中斷
      * addWaiter方法把當前線程封裝成Node,並添加到隊列的尾部
      */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

AQS - > tryAcquire

該方法嘗試獲取鎖,如果成功就返回,如果不成功,則把當前線程和等待狀態信息構造成一個Node節點,並將節點放入同步隊列的尾部。然後爲同步隊列中的當前節點循環等待獲取鎖,直到成功。

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

AQS - > nofairTryAcquire

    /**
     * Performs non-fair tryLock.  tryAcquire is implemented in
     * subclasses, but both need nonfair try for trylock method.
     */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        //獲取當前的狀態,默認情況下是0表示無鎖狀態
        int c = getState();
        if (c == 0) {
            //通過cas來改變state狀態的值,如果更新成功,表示獲取鎖成功
            //這個操作外部方法lock()就做過一次,這裏再做只是爲了再嘗試一次,儘量以最簡單的方式獲取鎖。
            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;
        }
        //如果狀態不爲0,且當前線程不是owner,則返回false
        return false;
    }

AQS - >addWaiter

當前線程來請求鎖的時候,如果鎖已經被其他線程鎖持有,當前線程會進入這個方法,這個方法主要是把當前線程封裝成node,添加到AQS的鏈表中。

    /**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    private Node addWaiter(Node mode) {
        //創建一個獨佔的Node節點,mode爲排他模式
        Node node = new Node(Thread.currentThread(), mode);
        // 嘗試快速入隊,如果失敗則降級至full enq
        Node pred = tail; // tail是AQS的中表示同步隊列隊尾的屬性,剛開始爲null,所以進行enq(node)方法
        if (pred != null) {
            node.prev = pred;
            // 防止有其他線程修改tail,使用CAS進行修改,如果失敗則降級至full enq
            if (compareAndSetTail(pred, node)) {
                pred.next = node; // 如果成功之後舊的tail的next指針再指向新的tail,成爲雙向鏈表    
                return node;
            }
        }
        // 如果隊列爲null或者CAS設置新的tail失敗
        enq(node);
        return node;
    }

AQS- >enq

enq就是通過自旋操作把當前節點加入到隊列中。

    /**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) {
        //無限循環,爲什麼採用for(;;),是因爲它執行的指令少,不佔用寄存器
        for (;;) {
            Node t = tail; //第一次循環時,head, tail都爲null
            //如果tail爲null則說明隊列首次使用,需要進行初始化
            if (t == null) { // Must initialize
                //設置頭節點,如果失敗則存在競爭,留至下一輪循環           
                if (compareAndSetHead(new Node()))
                    //用CAS的方式創建一個空的Node作爲頭結點,因爲此時隊列中只一個頭結點,所以tail也指向head
                    tail = head;
                    //第一次循環執行結束
            } else { 
                //進行第二次循環時,tail不爲null,進入else區域。
                //將當前線程的Node結點的prev指向tail,然後使用CAS將tail指向Node
                //這部分代碼和addWaiter代碼一樣,將當前節點添加到隊列
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    //t此時指向tail,所以可以CAS成功,將tail重新指向Node。
                    //此時t爲更新前的tail的值,即指向空的頭結點,t.next=node,就將頭結點的後續結點指向Node,返回頭結點
                    t.next = node;
                    return t;
                }
            }
        }
    }

AQS- >acquireQueued

前面addWaiter返回了插入的節點,作爲acquireQueued方法的入參,這個方法主要用於爭搶鎖。

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //獲取prev節點,若爲null即刻拋出NullPointException
                final Node p = node.predecessor();
                //如果前驅爲head纔有資格進行鎖的搶奪
                if (p == head && tryAcquire(arg)) {
                    //獲取鎖成功後就不需要再進行同步操作了,獲取鎖成功的線程作爲新的head節點
                    setHead(node);
                    //凡是head節點,head.thread與head.prev永遠爲null, 但是head.next不爲null
                    p.next = null; // help GC
                    failed = false; //獲取鎖成功
                    return interrupted;
                }
                //如果獲取鎖失敗,則根據節點的waitStatus決定是否需要掛起線程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //若前面爲true,則執行掛起,待下次喚醒的時候檢測中斷的標誌
                    interrupted = true;
            }
        } finally {
            if (failed)
                //如果拋出異常則取消鎖的獲取,進行出隊(sync queue)操作
                cancelAcquire(node);
        }
    }

原來的head節點釋放鎖以後,會從隊列中移除,原來head節點的next節點會成爲head節點。

AQS- >shouldParkAfterFailedAcquire

從上面的分析可以看出,只有隊列的第二個節點可以有機會爭用鎖,如果成功獲取鎖,則此節點晉升爲頭節點。對於第三個及以後的節點,if (p == head)條件不成立,首先進行shouldParkAfterFailedAcquire(p, node)操作。

shouldParkAfterFailedAcquire方法是判斷一個爭用鎖的線程是否應該被阻塞。它首先判斷一個節點的前置節點的狀態是否爲Node.SIGNAL,如果是,是說明此節點已經將狀態設置-如果鎖釋放,則應當通知它,所以它可以安全的阻塞了,返回true。
 

   /**
     * Checks and updates status for a node that failed to acquire.
     * Returns true if thread should block. This is the main signal
     * control in all acquire loops.  Requires that pred == node.prev.
     *
     * @param pred node's predecessor holding status
     * @param node the node
     * @return {@code true} if thread should block
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus; //前繼節點的狀態
        if (ws == Node.SIGNAL) //如果是SIGNAL狀態,意味着當前線程需要被unpark喚醒
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        /*
         * 如果前節點的狀態大於0,即爲CANCELLED狀態時,則會從前節點開始逐步循環找到一個沒有    
         * 被CANCELLED節點設置爲當前節點的前節點,返回false。在下次循環執行 
         * shouldParkAfterFailedAcquire時,返回true。
         * 這個操作實際是把隊列中CANCELLED節點剔除掉。
         */
        if (ws > 0) {
            /*
             * 如果前繼節點是“取消”狀態,則設置 “當前節點”的 “當前前繼節點” 爲 “‘原前繼節
             *  點'的前繼節點”。
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else { //如果前繼節點爲“0”或者“共享鎖”狀態,則設置前繼節點爲SIGNAL狀態。
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

AQS- >parkAndCheckInterrupt

如果shouldParkAfterFailedAcquire返回了true,acquireQueued會繼續執行parkAndCheckInterrupt()方法,它是通過LockSupport.park(this)將當前線程掛起到WATING狀態,它需要等待一箇中斷、unpark方法來喚醒它,通過這樣一種FIFO的機制的等待,來實現了Lock的操作。

   /**
     * Convenience method to park and then check if interrupted
     *
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        //LockSupport提供park()和unpark()方法實現阻塞線程和解除線程阻塞
        LockSupport.park(this);
        return Thread.interrupted();
    }

ReentrantLock - > unlock()

下面是釋放鎖的過程。先調用AQS - > release方法,這個方法裏面做兩件事,1.釋放鎖 ;2.喚醒park的線程。

   /**
     * Attempts to release this lock.
     *
     * <p>If the current thread is the holder of this lock then the hold
     * count is decremented.  If the hold count is now zero then the lock
     * is released.  If the current thread is not the holder of this
     * lock then {@link IllegalMonitorStateException} is thrown.
     *
     * @throws IllegalMonitorStateException if the current thread does not
     *         hold this lock
     */
    public void unlock() {
        sync.release(1);
    }
 
   /**
     * Releases in exclusive mode.  Implemented by unblocking one or
     * more threads if {@link #tryRelease} returns true.
     * This method can be used to implement method {@link Lock#unlock}.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryRelease} but is otherwise uninterpreted and
     *        can represent anything you like.
     * @return the value returned from {@link #tryRelease}
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

ReentrantLock - > Sync.tryRelease

這個動作可以認爲就是一個設置鎖狀態的操作,而且是將狀態減掉傳入的參數值(參數是1),如果結果狀態爲0,就將排它鎖的Owner設置爲null,以使得其它的線程有機會進行執行。 在排它鎖中,加鎖的時候狀態會增加1(當然可以自己修改這個值),在解鎖的時候減掉1,同一個鎖,在可以重入後,可能會被疊加爲2、3、4這些值,只有unlock()的次數與lock()的次數對應纔會將Owner線程設置爲空,而且也只有這種情況下才會返回true。
 

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases; // 這裏是將鎖的數量減1
            if (Thread.currentThread() != getExclusiveOwnerThread())
                // 如果釋放的線程和獲取鎖的線程不是同一個,拋出非法監視器狀態異常
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 由於重入的關係,不是每次釋放鎖c都等於0,
            // 直到最後一次釋放鎖時,纔會把當前線程釋放
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

lock與unlock過程總結

在獲取同步狀態時,同步器維護一個同步隊列,獲取狀態失敗的線程都會被加入到隊列中並在隊列中進行自旋;移出隊列(或停止自旋)的條件是前驅節點爲頭節點且成功獲取了同步狀態。在釋放同步狀態時,同步器調用tryRelease(int arg)方法釋放同步狀態,然後喚醒頭節點的後繼節點。

公平鎖和非公平鎖的區別

鎖的公平性是相對於獲取鎖的順序而言的,如果是一個公平鎖,那麼鎖的獲取順序就應該符合請求的絕對時間順序,也就是FIFO。 在上面分析的例子來說,只要CAS設置同步狀態成功,則表示當前線程獲取了鎖,而公平鎖則不一樣,差異點有兩個

        final void lock() {
            acquire(1);
        }

非公平鎖在獲取鎖的時候,會先通過CAS進行搶佔,而公平鎖則不會。

ReentrantLock - > FairSync.tryAcquire

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

這個方法與nonfairTryAcquire(int acquires)比較,不同的地方在於判斷條件多了hasQueuedPredecessors()方法,也就是加入了【同步隊列中當前節點是否有前驅節點】的判斷,如果該方法返回true,則表示有線程比當前線程更早地請求獲取鎖,因此需要等待前驅線程獲取並釋放鎖之後才能繼續獲取鎖。
 

Condition

我們知道任意一個Java對象,都擁有一組監視器方法(定義在java.lang.Object上),主要包括wait()、notify()以及notifyAll()方法,這些方法與synchronized同步關鍵字配合,可以實現等待/通知模式。

JUC包提供了Condition來對鎖進行精準控制,Condition是一個多線程協調通信的工具類,可以讓某些線程一起等待某個條件(condition),只有滿足條件時,線程纔會被喚醒。

condition中兩個最重要的方法:

  • await,把當前線程阻塞掛起;
  • signal,喚醒阻塞的線程。

AQS - > ConditionObject.await()
 

        /**
         * Implements interruptible condition wait.
         * <ol>
         * <li> If current thread is interrupted, throw InterruptedException.
         * <li> Save lock state returned by {@link #getState}.
         * <li> Invoke {@link #release} with saved state as argument,
         *      throwing IllegalMonitorStateException if it fails.
         * <li> Block until signalled or interrupted.
         * <li> Reacquire by invoking specialized version of
         *      {@link #acquire} with saved state as argument.
         * <li> If interrupted while blocked in step 4, throw InterruptedException.
         * </ol>
         */
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //創建一個新的節點,節點狀態爲condition,採用的數據結構仍然是鏈表
            Node node = addConditionWaiter(); 
            //釋放當前的鎖,得到鎖的狀態,並喚醒AQS隊列中的一個線程
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            //如果當前節點沒有在同步隊列上,即還沒有被signal,則將當前線程阻塞
            //isOnSyncQueue 判斷當前 node 狀態,如果是 CONDITION 狀態,或者不在隊列上了,就繼續阻塞。
            //還在隊列上且不是 CONDITION 狀態了,就結束循環和阻塞
            while (!isOnSyncQueue(node)) { //第一次判斷的是false,因爲前面已經釋放鎖了
                LockSupport.park(this); //第一次總是 park 自己,開始阻塞等待
                //線程判斷自己在等待過程中是否被中斷了
                //如果沒有中斷,則再次循環,會在 isOnSyncQueue 中判斷自己是否在隊列上
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //當這個線程醒來,會嘗試拿鎖, 當 acquireQueued 返回 false 就是拿到鎖了
            // interruptMode != THROW_IE -> 表示這個線程沒有成功將 node 入隊,但 signal 執行了 enq 方法讓其入隊了
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                // 將這個變量設置成 REINTERRUPT
                interruptMode = REINTERRUPT;
            //如果 node 的下一個等待者不是null, 則進行清理,清理 Condition 隊列上的節點
            //如果是 null ,就沒有什麼好清理的了
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            //如果線程被中斷了,需要拋出異常.或者什麼都不做
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

AQS - > ConditionObject.signal()

調用Condition的signal()方法,將會喚醒在等待隊列中等待時間最長的節點(首節點),在喚醒節點之前,會將節點移到同步隊列中。

       /**
         * Moves the longest-waiting thread, if one exists, from the
         * wait queue for this condition to the wait queue for the
         * owning lock.
         *
         * @throws IllegalMonitorStateException if {@link #isHeldExclusively}
         *         returns {@code false}
         */
        public final void signal() {
            //先判斷當前線程是否獲得了鎖
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter; //拿到 Condition 隊列上第一個節點
            if (first != null)
                doSignal(first);
        }
 
        /**
         * Removes and transfers nodes until hit non-cancelled one or
         * null. Split out from signal in part to encourage compilers
         * to inline the case of no waiters.
         * @param first (non-null) the first node on condition queue
         */
        private void doSignal(Node first) {
            do {
                //如果第一個節點的下一個節點是 null,那麼最後一個節點也是 null
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null; //將 next 節點設置成 null
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

AQS - > transferForSignal

該方法先是 CAS 修改了節點狀態,如果成功,就將這個節點放到 AQS 隊列中,然後喚醒這個節點上的線程。此時,那個節點就會在 await 方法中甦醒。

 /**
     * Transfers a node from a condition queue onto sync queue.
     * Returns true if successful.
     * @param node the node
     * @return true if successfully transferred (else the node was
     * cancelled before signal)
     */
    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        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).
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        // 如果上一個節點的狀態被取消了, 或者嘗試設置上一個節點的狀態爲 SIGNAL 失敗了
        // SIGNAL 表示: 他的next 節點需要停止阻塞
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread); // 喚醒輸入節點上的線程
        return true; 
    }

LockSupport

LockSupport類是Java6引入的一個類,提供了基本的線程同步原語。LockSupport實際上是調用了Unsafe類裏的函數,歸結到Unsafe裏,只有兩個函數:

public native void unpark(Thread jthread);
 
public native void park(boolean isAbsolute, long time);

unpark函數爲線程提供“許可(permit)”,線程調用park函數則等待“許可”。這個有點像信號量,但是這個“許可”是不能疊加的,“許可”是一次性的。

permit相當於0/1的開關,默認是0,調用一次unpark就加1變成了1.調用一次park會消費permit,又會變成0。 如果再調用一次park會阻塞,因爲permit已經是0了。直到permit變成1.這時調用unpark會把permit設置爲1。每個線程都有一個相關的permit,permit最多隻有一個,重複調用unpark不會累積。

在使用LockSupport之前,我們對線程做同步,只能使用wait和notify,但是wait和notify其實不是很靈活,並且耦合性很高,調用notify必須要確保某個線程處於wait狀態,而park/unpark模型真正解耦了線程之間的同步,先後順序沒有沒有直接關聯,同時線程之間不再需要一個Object或者其它變量來存儲狀態,不再需要關心對方的狀態。

總結

1. 公平鎖

  •  線程1進來時會調用acquire方式嘗試獲取鎖,此時會獲取成功,把線程設置爲當前前程,並更改計數
  • 線程2進來會判斷嘗試獲取鎖,如果state=0&&hasQueuedPredecessors 沒有等待隊列,則當前線程可以嘗試CAS,如果state=0但是有等待隊列,則認爲輪不到t2執行,調用addWaiter追加到隊列(這裏區分是首次追加和非首次追加,首次追加需要構造出一個虛擬節點),構造完成後return,然後調用acquireQueued進行循環,順便再判斷剛添加進來這個節點是否可以被執行,如果可以,將t2出隊,並移動head指針,如果不可以被執行,則設置阻塞狀態。
  • 線程3進來,同線程2,只不過這裏會調用addWaiter直接追加到隊列尾部(因爲已經有線程2在排隊,輪不到線程3執行了)。
  • 線程1釋放鎖時,會給state進行減1操作,判斷head指針即判斷隊列中是否有排隊的線程,有則喚醒。

 2.非公平鎖 

  • 非公平鎖在調用 lock 後,首先就會調用 CAS 進行一次搶鎖,如果這個時候恰巧鎖沒有被佔用,那麼直接就獲取到鎖返回了。
  • 非公平鎖在 CAS 失敗後,和公平鎖一樣都會進入到 tryAcquire 方法,在 tryAcquire 方法中,如果發現鎖這個時候被釋放了(state == 0),非公平鎖會直接 CAS 搶鎖,但是公平鎖會判斷等待隊列是否有線程處於等待狀態,如果有則不去搶鎖,乖乖排到後面。

公平鎖和非公平鎖就這兩點區別,如果這兩次 CAS 都不成功,那麼後面非公平鎖和公平鎖是一樣的,都要進入到阻塞隊列等待喚醒。

相對來說,非公平鎖會有更好的性能,因爲它的吞吐量比較大。當然,非公平鎖讓獲取鎖的時間變得更加不確定,可能會導致在阻塞隊列中的線程長期處於飢餓狀態。

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