1. 程式人生 > >Java併發程式設計(八)ReentrantLock

Java併發程式設計(八)ReentrantLock

一、ReentrantLock簡介

ReentrantLock可重入鎖,全名java.util.concurrent.locks.ReentrantLock,相當於是個最基礎版本的Lock的實現,針對公平鎖和非公平鎖,ReentrantLock都有實現。

二、ReentrantLock特性

1、可輪詢鎖和定時鎖

可以通過呼叫trylock方法,查詢鎖的狀態,如果鎖已經被其他執行緒持有,則不會一直阻塞下去,避免死鎖。

2、公平鎖和非公平鎖

公平鎖是按照發出請求的順序獲取鎖,不允許插隊;而非公平鎖允許插隊,二者各有優缺點,ReentradntLock都有實現。

公平鎖的作用就是嚴格按照執行緒啟動的順序來執行的,不允許其他執行緒插隊執行的;而非公平鎖是允許插隊的,但由於可以減少執行緒恢復和掛起操作,總體執行效率更高。

3、可中斷鎖

lockInterruptibly方法能夠在獲取鎖的同時保持對中斷的響應,因此無需建立其它型別的不可中斷阻塞操作。

三、ReentrantLock原理

ReentrantLock的架構相對簡單,主要包括一個Sync的內部抽象類以及Sync抽象類的兩個實現類。Sync繼承自AQS,Sync的兩個實現類分別是NonfairSync和FairSync,即非公平鎖和公平鎖。下面分別針對非公平鎖和公平鎖講一下ReentrantLock的原理。

1、非公平鎖的lock

非公平鎖lock方法原始碼如下:

// 非公平鎖的lock方法
// java.util.concurrent.locks.ReentrantLock.NonfairSync#lock
final void lock() {
	if (compareAndSetState(0, 1))
		setExclusiveOwnerThread(Thread.currentThread());
	else
		acquire(1);
}

// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
	if (!tryAcquire(arg) &&
			acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
}

呼叫過程整理,總共分為兩步:

第一步,直接嘗試CAS搶佔鎖,如果搶佔成功,返回成功,如果搶佔失敗,則進行第二步;

第二步,呼叫acquire方法,這個方法內做了兩件事,第一件事,先呼叫tryAcquire方法嘗試搶佔鎖,如果失敗則第二件事,即先呼叫addWaiter方法將執行緒節點加入同步佇列,然後進入acquireQueued方法嘗試搶佔鎖。

下面分別講這三個方法,tryAcquire、addWaiter和acquireQueued。

tryAcquire方法內,先判斷當前鎖是否已被搶佔,如果未被搶佔,那麼就CAS嘗試搶佔鎖,如果搶佔成功,那麼就設定當前執行緒為已搶佔鎖的執行緒;如果鎖還未被搶佔,那麼判斷當前執行緒是不是已獲得鎖的執行緒,如果是,那麼就更新同步狀態位,否則搶佔失敗。tryAcquire方法原始碼如下:

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

// java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
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;
}

addWaiter方法內,先判斷當前佇列是否為空,如果為空,則直接CAS設定當前節點為頭結點;如果佇列不為空,則CAS設定當前節點為尾節點。addWaiter方法原始碼如下:

// java.util.concurrent.locks.AbstractQueuedSynchronizer#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(node);
	return node;
}

private Node enq(final Node node) {
	for (;;) {
		Node t = tail;
		if (t == null) { // Must initialize
			if (compareAndSetHead(new Node()))
				tail = head;
		} else {
			node.prev = t;
			if (compareAndSetTail(t, node)) {
				t.next = node;
				return t;
			}
		}
	}
}

acquireQueued方法內,在一個死迴圈內,不斷判斷當前節點的前驅節點是否是頭結點,如果是,則表示當前節點可搶佔鎖,會呼叫tryAcquire方法嘗試搶佔鎖。acquireQueued方法原始碼如下:

// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued
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);
	}
}

2、公平鎖的lock

公平鎖與非公平鎖的實現有兩個差別,一是不會直接搶佔,而是隻呼叫acquire方法;二是tryAcquire方法實現有差別,上面講到非公平鎖在搶佔鎖時,如果當前無執行緒佔鎖,那麼就直接CAS搶佔鎖,而非公平鎖需要先判斷當前節點是否無前驅節點,如果沒有前驅節點才可以搶佔鎖,這是為了保證FIFO。原始碼如下:

// 公平鎖lock,其中,acquire方法與非公平鎖是同一個
final void lock() {
	acquire(1);
}

// java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
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;
}

3、unlock

unlock是不區分公平鎖和非公平鎖的,unlock的時候做兩件事,一是同步狀態更新並setExclusiveOwnerThread(null)設定當前持有鎖的執行緒為null,二是喚醒佇列中的後續節點搶佔,unlock鎖原始碼如下:

public void unlock() {
	sync.release(1);
}

// java.util.concurrent.locks.AbstractQueuedSynchronizer.release()
public final boolean release(int arg) {
	if (tryRelease(arg)) {
		Node h = head;
		if (h != null && h.waitStatus != 0)
			unparkSuccessor(h);
		return true;
	}
	return false;
}

// java.util.concurrent.locks.ReentrantLock.Sync#tryRelease
protected final boolean tryRelease(int releases) {
	int c = getState() - releases;
	if (Thread.currentThread() != getExclusiveOwnerThread())
		throw new IllegalMonitorStateException();
	boolean free = false;
	if (c == 0) {
		free = true;
		setExclusiveOwnerThread(null);
	}
	setState(c);
	return free;
}

 

注:

本文中所有原始碼均為jdk1.7.0_79版本。