前言
在說ReentrantLock之前,我們先說說併發吧。
在JDK1.5之前,併發處理常用的關鍵字synchronized。使用synchronized關鍵字,鎖的獲取和釋放是隱式的,synchronized主要通過系統的monitorenter指令實現的。
那時候synchronized可以稱爲重量級鎖,執行效率不是很高。
而Doug Lea編寫的util.concurrent 包被納入JSR-166標準。這裏面就包含了ReentrantLock。
ReentrantLock爲編寫併發提供了更多選擇。
使用
ReentrantLock的通常用法如下:
public class X {
private final ReentrantLock lock = new ReentrantLock();
public void m() {
lock.lock();
try {
//TODO
} finally {
lock.unlock();
}
}
}
原理
ReentrantLock主要是通過AbstractQueuedSynchronizer實現的,是一個重入鎖,即一個線程加鎖後仍然可以獲得鎖,不會出現自己阻塞自己的情況。
UML圖
我們看一下它們的UML圖。
可以看到ReentrantLock實現了Lock接口。
鎖類型
ReentrantLock的兩種鎖類型,公平鎖和非公平鎖。
源碼分析
我們先來看下ReentrantLock的構造方法。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可以看到默認無參構造方法爲非公平鎖實現。如果想定義公平鎖實現,可以傳入true來控制。
它的lock方法:
public void lock() {
sync.acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
公平鎖和非公平鎖各有自己的實現方式。我們來看下他們的tryAcquire方法。
非公平鎖源碼:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
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;
}
可以看到,非公平鎖首先判斷AQS(AbstractQueuedSynchronizer)中的state是否爲0,0表示沒有線程持有該鎖,當前線程就嘗試獲取鎖。
如果不是0,那在判斷是不是當前線程持有該鎖,如果是,就會增加state,改變state狀態。(因此ReentranLock支持重入)。
公平鎖源碼:
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
@ReservedStackAccess
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;
}
}
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
公平鎖的tryAcquire方法,可以看到,相比非公平鎖,多了hasQueuedPredecessors方法,這個方法是判斷隊列中是否有其他線程,如果沒有,線程纔會嘗試獲取鎖,如果有,會先把鎖分配給隊列的線程,因此稱爲公平鎖。
這兒可以看到,非公平鎖的效率比公平鎖要高。
這是tryAcquire方法,如果嘗試獲取鎖失敗了呢?
那就會執行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法啦。
我們先來看一下addWaiter方法。
private Node addWaiter(Node mode) {
Node node = new Node(mode);
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
node.setPrevRelaxed(oldTail);
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {
initializeSyncQueue();
}
}
}
可以看到,這個方法會把線程添加到隊列尾,同時,for(;;)循環保證添加成功,直到return出去。
添加後,調用acquireQueued方法,這個方法爲掛起等待線程。
看下該方法源碼:
final boolean acquireQueued(final Node node, int arg) {
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
可以看到,如果節點爲頭節點,就嘗試獲取一次鎖,如果成功,就返回。
否則判斷該線程是否需要掛起,如果需要的化就調用parkAndCheckInterrupt掛起。
調用LockSupport.park方法掛起線程,直到被喚醒。
selfInterrupt方法:
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0();
b.interrupt(this);
return;
}
}
interrupt0();
}
調用interrupt方法,中斷正在執行的線程(如果不是當前線程的話)。
釋放鎖unlock方法:
公平鎖和非公平鎖釋放鎖的方法是一樣的。
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
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) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
可以看到首先會判斷當前線程是否是獲得鎖的線程,如果是重入鎖需要將state減完纔算是完全釋放鎖。
釋放後調用unparkSuccessor喚起掛起線程。
總結
- 非公平鎖的效率是比公平鎖要高的。
- ReentranLock支持重入,因爲增加了對自身線程的處理,通過state可以控制。
- 解鎖操作應放到finally塊裏,避免使用鎖時出現資源無法釋放的問題。