java concurrent (1): 鎖機制

JDK5之前多線程的鎖都是使用synchronized ,JDK 5中的鎖是接口java.util.concurrent.locks.Lock。另外java.util.concurrent.locks.ReadWriteLock提供了一對可供讀寫併發的鎖。ReentrantLock是java.util.concurrent.locks中的一個可重入鎖類。在高競爭條件下有更好的性能,且可以中斷。深入剖析ReentrantLock的源碼有助於我們瞭解線程調度,鎖實現,中斷,信號觸發等底層機制,實現更好的併發程序。 

先來看ReentrantLock最常用的代碼lock 

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

代碼很簡單,直接調用成員變量sync的lock方法

 /** Synchronizer providing all implementation mechanics */
    private final Sync sync;

Sync 是ReentrantLock的抽象靜態內部類繼承了AbstractQueuedSynchronizer ,後面會看到很多操作其實都是通過AbstractQueuedSynchronizer 來實現的,AbstractQueuedSynchronizer 是一個很重要的類型,concurrent 包裏很多實現都依賴他。完整的設計思想可以參考 http://gee.cs.oswego.edu/dl/papers/aqs.pdf  。FairSync和NonFairSync則是具體的子類,分別對應了公平鎖和非公平鎖。其實這兩個都差不多,瞭解其中一個去看另一個其實差不多。實際中公平鎖吞吐量比非公平鎖小很多,所以以下分析以非公平鎖爲例。 在說具體的實現前不得不說AbstractQueuedSynchronizer, 最重要的兩個數據成員當前鎖狀態和等待鏈表都是由它來實現的。 

/**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;

    /**
     * The synchronization state.
     */
    private volatile int state;

state記錄了當前鎖被鎖定的次數。如果爲0則未被鎖定。加鎖通過更改狀態實現,而更改狀態主要由函數compareAndSetState實現,調用cas原語以保證操作的原子性。Node是靜態內部類,重要的字段如下

/**
     * 節點的等待狀態,一個節點可能位於以下幾種狀態:
     * CANCELLED = 1: 節點操作因爲超時或者對應的線程被interrupt。節點不應該不留在此狀態,一旦達到此狀態將從CHL隊列中踢出。
     * SIGNAL = -1: 節點的繼任節點是(或者將要成爲)BLOCKED狀態(例如通過LockSupport.park()操作),因此一個節點一旦被釋放(解鎖)或者取消就需要喚醒(LockSupport.unpack())它的繼任節點。
     * CONDITION = -2:表明節點對應的線程因爲不滿足一個條件(Condition)而被阻塞。
     * 0: 正常狀態,新生的非CONDITION節點都是此狀態。
     * 非負值標識節點不需要被通知(喚醒)。
     */
    volatile int waitStatus;
	/**
     * 此節點的前一個節點。節點的waitStatus依賴於前一個節點的狀態。
     */
    volatile Node prev;

    /**
     * 此節點的後一個節點。後一個節點是否被喚醒(uppark())依賴於當前節點是否被釋放。
     */
    volatile Node next;

    /**
     * 節點綁定的線程。
     */
    volatile Thread thread;

    /**
     * 下一個等待條件(Condition)的節點,由於Condition是獨佔模式,
     * 因此這裏有一個簡單的隊列來描述Condition上的線程節點。
     */
    Node nextWaiter;

而另一個重要的屬性則在AbstractQueuedSynchronizer 的父類AbstractOwnableSynchronizer中。 

/**
     * The current owner of exclusive mode synchronization.
     */
    private transient Thread exclusiveOwnerThread;

瞭解這些基礎之後,來看NonFairSync 的 lock函數

/**
	 * Performs lock.  Try immediate barge, backing up to normal
	 * acquire on failure.
	 */
	final void lock() {
		//如果鎖沒有被任何線程鎖定,則用cas方式進行搶佔
		if (compareAndSetState(0, 1))
			//如果獲取鎖成功則設定當前線程爲鎖的擁有者 
			setExclusiveOwnerThread(Thread.currentThread());
		else
			//如果鎖已經被佔用,則嘗試加鎖,
			acquire(1);
	}

這裏說下acquir方法,這個方法由AbstractQueuedSynchronizer 提供

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
1.如果tryAcquire(arg)成功,那就沒有問題,已經拿到鎖,整個lock()過程就結束了。如果失敗進行操作2。

2. addWaiter創建一個獨佔節點(Node)並且此節點加入CHL隊列末尾(稍後分析)。進行操作3。

3. acquireQueued 自旋嘗試獲取鎖,失敗根據前一個節點來決定是否掛起(park()),直到成功獲取到鎖。進行操作4。

4.selfInterrupt 如果當前線程已經中斷過,那麼就中斷當前線程(清除中斷位)。

這裏有點複雜,接下來會一步一步分析。

tryAcquire 被NonFairSync override ,直接調用 Sync.nonfairTryAcquire,代碼如下 

 /**
         * 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();
            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;
        }
有點似曾相識的感覺,跟NonFairSync 的 lock函數有點類似。如果該方法失敗返回false,也就是tryAcquire失敗,進行acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 。先來看addWaiter函數

/**
     * Creates and enqueues node for given thread and mode.
     *
     * @param current the thread
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    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)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
將當前線程封裝成一個node然後放入AbstractQueuedSynchronizer的node隊列。這裏考慮了隊列爲空和多線程併發的情況,所以處理的比較糾結,不過代碼倒是不復雜,耐心看可以理解。上面是節點如隊列的一部分。當前僅當隊列不爲空並且將新節點插入尾部成功後直接返回新節點。否則進入enq(Node)進行操作。

/**
     * 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 (;;) {
            Node t = tail;
	    //如果爲空就創建頭結點
            if (t == null) { // Must initialize
                Node h = new Node(); // Dummy header
                h.next = node;
                node.prev = h;
                if (compareAndSetHead(h)) {
                    tail = node;
                    return h;
                }
            }
	    //如果這個時候因爲併發,隊列已經非空,那就把當前的node放入隊尾
            else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }


接着就是調用acquireQueued 方法,讓線程進入禁用狀態,並在每次被喚醒時嘗試獲取鎖,失敗則繼續禁用線程。 

/**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
		// 如果當前node是head的直接後繼則嘗試獲取鎖  
		// 這裏不會和等待隊列中其它線程發生競爭,但會和嘗試獲取鎖且尚未進入等待隊列的線程發生競爭。這是非公平鎖和公平鎖的一個重要區別。  
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
		// 如果不是head的後繼或獲取鎖失敗,則檢查是否要禁用當前線程  
                // 是則禁用,直到被lock.release喚醒或線程中斷  
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
    }
shouldParkAfterFailedAcquire做了一件很重要的事:根據狀態對等待隊列進行清理,並設置等待信號。這裏需要先說明一下waitStatus,它是AbstractQueuedSynchronizer的靜態內部類Node的成員變量,用於記錄Node對應的線程等待狀態.等待狀態在剛進入隊列時都是0,如果等待被取消則被設爲Node.CANCELLED,若線程釋放鎖時需要喚醒等待隊列裏的其它線程則被置爲Node.SIGNAL,還有一種狀態Node.CONDITION這裏先不討論。

 /**
     * 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)
            /*
		*如果前一個節點的等待狀態waitStatus 被設置爲SIGNAL,也就是前面的節點還沒有獲得到鎖,
		*那麼返回true,表示當前節點(線程)就應該park()了
             */
            return true;
        if (ws > 0) {
            /*
             * 如果前一個節點的等待狀態waitStatus>0,也就是前一個節點被CANCELLED了,
	     *那麼就將前一個節點去掉,遞歸此操作直到所有前一個節點的waitStatus<=0。
             */
			do {
				node.prev = pred = pred.prev;
			} while (pred.waitStatus > 0);
			pred.next = node;
        } else { //等於0的時候
			//前一個節點等待狀態waitStatus=0,修改前一個節點狀態位爲SINGAL,
			//表示後面有節點等待處理,需要根據它的等待狀態來決定是否該park()
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        } 
		/* 
		 *這裏一定要返回false,有可能前置結點這時已經釋放了鎖,
		 *但因其 waitStatus在釋放鎖時還未被置爲SIGNAL而未觸發喚醒等待線程操作,
		 *因此必須通過return false來重新嘗試一次獲取鎖  
		*/
		return false;
    }
如果shouldParkAfterFailedAcquire 返回true那麼會調用parkAndCheckInterrupt 。實現如下,很簡單,直接禁用線程,並等待被喚醒或中斷髮生。對java中Thread.interrupted()都作了什麼不甚瞭解的要做功課。 這裏線程即被堵塞,醒來時會重試獲取鎖,失敗則繼續堵塞。即使Thread.interrupted()也無法中斷。那些想在等待時間過長時中斷退出的線程可以調用ReentrantLoc.lockInterruptibly()。  

/**
     * Convenience method to park and then check if interrupted
     *
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

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