ReentrantLock是juc包里的一種重要的鎖擅羞∪卤ぃ可重入鎖蝗罗,顧名思義,就是一個線程可以重復(fù)進入該鎖所保護的臨界資源蝌戒。下面通過源碼閱讀串塑,來一步一步看是怎么實現(xiàn)的。
uml圖
ReentrantLock實現(xiàn)了Lock和serializable接口北苟,同時其主要操作委托給其內(nèi)部類Sync來執(zhí)行桩匪。Sync有兩個子類FairSync和NonfairSync,即公平鎖和非公平鎖友鼻。Sync同時又繼承了AbstractQueuedSynchronizer,java中的一個重要的類傻昙,簡稱AQS。該類主要維護一個雙向的鏈表彩扔,表的Node為等待獲取鎖的線程妆档。所有新入隊的Node都在表為,即tail所在的位置虫碉。所有自行的線程贾惦,都放在表頭,即head所指的位置。
一個例子進入學(xué)習(xí)
import java.util.concurrent.locks.ReentrantLock;
public class TestReenterLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static int i = 0;
@Override
public void run() {
for (int j = 0; j < 100000; j++) {
lock.lock();
try {
i++;
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
TestReenterLock instance = new TestReenterLock();
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
代碼例子中纤虽,操作臨界資源前乳绕,通過lock.lock()方法獲取鎖,操作完成后逼纸,通過unlock方法釋放鎖洋措。
接下來看lock方法的實現(xiàn)。
lock方法
ReentrantLock中的lock方法很簡單杰刽。
public void lock() {
sync.lock();
}
其委托給sync的lock方法菠发。
由上圖可以看到,Sync是一個繼承至AQS的類abstract static class Sync extends AbstractQueuedSynchronizer
贺嫂,其為一個抽象的類滓鸠。有兩個子類,F(xiàn)airSync和NonfairSync第喳,即公平鎖和非公平鎖糜俗。
首先看非公平鎖,也是默認(rèn)的實現(xiàn)曲饱。
final void lock() {
//嘗試獲取鎖悠抹,通過修改AQS的狀態(tài)
if (compareAndSetState(0, 1))
//獲取成功,將當(dāng)前的線程置為獨占的
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
compareAndSetState
為AQS里的方法扩淀,源碼實現(xiàn)如下:
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
這里用了unsafe的相關(guān)操作楔敌。compareAndSwapInt有四個參數(shù),分別是需要修改的對象驻谆,對象的內(nèi)存地址卵凑,期望的原始值和更新后的值。
回到lock方法胜臊,當(dāng)其沒有獲取到資源時勺卢,則調(diào)用acquire方法:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
上面的代碼意思是再一次嘗試獲取資源,若沒有獲取到象对,則嘗試加入隊列中值漫。
tryAcquire代碼如下:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//獲取當(dāng)前線程
final Thread current = Thread.currentThread();
//獲取當(dāng)前鎖狀態(tài)
int c = getState();
//當(dāng)前狀態(tài)為0,沒有線程持有鎖织盼,可以獲取鎖了
if (c == 0) {
// 修改狀態(tài),獲取資源
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//若已有線程持有鎖酱塔,則判斷是否是當(dāng)前線程持有的沥邻,若是,則修改狀態(tài)羊娃,在原有狀態(tài)上+1
//該處體現(xiàn)了重入鎖的概念了唐全。
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;
}
上面的一段代碼體現(xiàn)了重入鎖重入的概念。即如果發(fā)現(xiàn)是當(dāng)前線程持有了鎖,則線程再一次獲取鎖的時候邮利,再狀態(tài)上加一弥雹,釋放的時候?qū)tate減1,所以延届,當(dāng)對一個線程多次獲取鎖時剪勿,需要對應(yīng)次數(shù)的unlock操作,如若不然方庭,可能會使其他線程一直無法獲取鎖厕吉。如下:
lock.lock();
lock.lock();
try{
業(yè)務(wù)處理代碼
}finally{
lock.unlock();
lock.unlock();
}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))源碼如下:
addWaiter如下:
private Node addWaiter(Node mode) {
//創(chuàng)建一個node
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) {
//將當(dāng)前的node的前驅(qū)設(shè)置為隊列的尾部
node.prev = pred;
//設(shè)置tail指向當(dāng)前node
if (compareAndSetTail(pred, node)) {
//設(shè)置原tail指向的節(jié)點的next指向當(dāng)前node,形成一個雙向鏈表械念。
pred.next = node;
//返回當(dāng)前節(jié)點
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;
}
}
}
}
將自己的node放到等待隊列尾部头朱。
acquireQueued的代碼如下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//獲取node的前驅(qū)節(jié)點
final Node p = node.predecessor();
//若前驅(qū)節(jié)點是隊列的頭節(jié)點,且能獲取到資源龄减,則將當(dāng)前節(jié)點設(shè)置為頭節(jié)點项钮,并返回,退出循環(huán)希停。
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//設(shè)置當(dāng)前節(jié)點的等待狀態(tài)烁巫,并調(diào)用LockSupport.park(this)方法,阻塞當(dāng)前線程脖苏。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
以上是非公平鎖的代碼程拭,非公平體現(xiàn)在何處呢?
非公平體現(xiàn)在棍潘,當(dāng)前線程執(zhí)行完之后恃鞋,喚醒隊列頭的等待隊列時,如果有新線程來了亦歉,正好獲取到資源了恤浪,則會把該資源放到隊頭,進行執(zhí)行肴楷,而不是遵循著先到先執(zhí)行的原則水由。公平鎖就是按照隊列頭開始,一個節(jié)點一個節(jié)點的往下執(zhí)行赛蔫,先到先執(zhí)行的意思在里邊砂客。
公平鎖對應(yīng)的代碼如下:
final void lock() {
acquire(1);
}
相對于非公平鎖來說,少了獲取資源的判斷呵恢,這樣就不會有線程插隊鞠值,而是老老實實的喚醒隊頭的線程。
unlock方法
unlock也是委托給sync的unlock方法渗钉。unlock沒有公不公平這一說彤恶。
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;
}
tryRelease方法是試圖釋放鎖钞钙。代碼如下:
protected final boolean tryRelease(int releases) {
//同一線程多次獲取鎖,在釋放的時候声离,需要多次unlock芒炼,體現(xiàn)在這個地方
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;
}
Node的waitStatus 有如下狀態(tài)值:
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
以上代碼大意就是從隊尾開始向前遍歷,找到離隊頭最近的一個沒有被cannel的線程术徊,調(diào)用unpark方法喚醒該等待隊列本刽。
到此,ReentrantLock差不多讀完了弧关。