ReentrantLock和AQS源碼解析

ReentrantLock源碼

  • synchronized通過在對象頭的markword進行操作從而實現互斥鎖
  • ReentrantLock通過將線程加入AQS阻塞隊列從而實現同步互斥鎖

首先初始化一個ReentrantLock

ReentrantLock lock = new ReentrantLock(true);

這個時候是默認的構造函數,新建一個公平鎖

public ReentrantLock(boolean fair){
    sync = fair ? new FairSync() : new NonfairSync();
}

然後加鎖操作lock.lock(),這個方法會去調用FairSync下面的lock()方法

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

接着我們進入這個acquire(1)去看看,原來它是AQS的一個方法

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

首先第一個判斷是tryAcquire(arg),我們應該去它的子類,也就是Sync去看,這個時候也就是去FairSync類的tryAcquire(arg)去看

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;
}

AQS源碼(自旋+CAS+park/unpark)

AQS(AbstractQueuedSynchronizer)類的設計主要構成

// 隊首
private transient volatile Node head;
// 隊尾
private transient volatile Node tail;
// 鎖狀態
private volatile int state;
// 持有鎖的那個線程(這個屬性其實是在AbstractOwnableSynchronizer類裏的)
private transient Thread exclusiveOwnerThread;

AQS中的入隊操作源碼解析

addWaiter
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使第一個線程入隊
	enq(node);
    return node;
}
enq
// 通過這個函數我們可以看出AQS中的head一直都是指向一個空的Node節點
private Node enq(final Node node) {
    // 若當前隊列爲空,那麼for循環會執行兩次,第一次執行if(將head指向一個空的Node),第二次執行else(將當前的node連接到上個空Node之後,也就是入隊)
	for (;;) {
		Node t = tail;
        // 第一次會執行if(將head指向一個空的Node)
		if (t == null) { // Must initialize
			if (compareAndSetHead(new Node()))
				tail = head;
		}
        // 第二次會執行else(將當前的node連接到上個空Node之後,也就是入隊)
        else {         
            // 連接到上一個空Node,即入隊
        	node.prev = t;
            // 將隊列尾節點指向當前節點node
            if (compareAndSetTail(t, node)) {
            	t.next = node;
            	return t;
            }
		}
	}
}

入隊後的隊列結構如下圖所示:注意,隊列頭爲空節點,代表的是當前獲得鎖的線程。也可以理解爲持有鎖的線程永遠不會在隊列裏(保證持有鎖的線程不參與排隊的原則),比如這個時候t1拿到鎖了,那麼t1會被置位head,並且裏面的值全爲null(見acquireQueued方法中的setHead方法)

多線程非交替執行下。比如t1線程獲取了ReentrantLock鎖,這個時候t2線程來了,對t2的處理流程圖如下圖所示:

總結

ReentrantLocksynchronized快,是因爲前者的加鎖操作是在JDK的工作層次下操作的,而後者需要調用操作系統去工作。

  • ReentrantLock中一部分操作是在JDK的層面下解決的,還有一部分也是在操作系統中解決的;
    • 多線程交替執行(不會交叉,比如線程t1執行完了t2纔開始執行,這個時候和隊列沒有關係):比如上面貼出的AQS中的acquire方法,首先是執行tryAcquire這個方法是不會調用操作系統的,如果這個方法返回true了,那就不會再繼續判斷後面調用操作系統的的acquireQueued方法
    • 多線程不是交替執行的:這個時候就會AQS中的隊列就不爲空了。

synchronized在JDK1.6之前全都要經過操作系統來解決,所以那個時候提出了ReentrantLock

ReentrantLock.lockTnterruptibly()

if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章