ReentrantLock簡單使用demo如下:
Lock lock = new ReentrantLock();
lock.lock();
try {
//業(yè)務邏輯
} finally {
lock.unlock();
}
注:獲取的鎖代碼要放到try塊之外由驹,防止獲得鎖代碼異常珍坊,拋出異常的同時社露,也會導致一次鎖的釋放猜嘱。釋放代碼一定要放到finally塊中讯泣。
** AQS **
了解java中的鎖纫普,首先的了解AQS方淤。
AQS(AbstractQueuedSynchronizer)隊列同步器甲雅。是用來構(gòu)建鎖或者其它同步組件的基礎框架,他實現(xiàn)了一個int成員變量標識同步狀態(tài)(更改這個變量值來獲取和釋放鎖)论皆,通過內(nèi)置的FIFO雙向隊列來完成資源獲取線程排隊的工作拳锚。
AQS可以實現(xiàn)獨占鎖和共享鎖假栓,RenntrantLock實現(xiàn)的是獨占鎖,ReentrantReadWriteLock實現(xiàn)的是獨占鎖和共享鎖晌畅,CountDownLatch實現(xiàn)的是共享鎖但指。
ReentrantLock 類結(jié)構(gòu)信息如下圖:
- ReentrantLock 實現(xiàn) Lock 和 Serializable 接口
- RentrantLock 有三個內(nèi)部類 Sync、NonfairSync 和 FairSync 類
- Sync 繼承 AbstractQueuedSynchronizer 抽象類
- NonfairSync(非公平鎖) 繼承 Sync 抽象類
- FairSync(公平鎖) 繼承 Sync 抽象類
** 公平鎖和非公平鎖 **
ReentrantLock 有兩種實現(xiàn)方式,公平鎖和非公平鎖棋凳。
公平鎖:當前線程不立刻獲得鎖拦坠,而是先直進入等待隊列中隊尾進行排隊獲取鎖。
非公平鎖:當前線程首先嘗試獲取一下鎖(僅僅嘗試一下)剩岳,如果獲取不到贞滨,則乖乖的進入到等待隊列中去排隊。
ReentrantLock實現(xiàn)公平鎖和非公平鎖代碼如下:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
** 獲取非公平鎖 **
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
- 首先通過CAS更新AQS中的state變量來獲得鎖(第一次獲得鎖)拍棕,如果獲取成功則把當前線程設置為獨占鎖
- 如果是設置失敗晓铆,進入到acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 首先執(zhí)行tryAcquire方法,嘗試獲得鎖绰播。
- 如果獲取失敗則進入addWaiter方法骄噪,構(gòu)造同步節(jié)點,將該節(jié)點添加到同步隊列尾部蠢箩,并返回此節(jié)點链蕊,進入acquireQueued方法。
- acquireQueued方法谬泌,這個新節(jié)點死是循環(huán)的方式獲取同步狀態(tài)滔韵,如果獲取不到則阻塞節(jié)點中的線程,阻塞后的節(jié)點等待前驅(qū)節(jié)點來喚醒掌实。
/**
* 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;
}
tryAcquire調(diào)用nonfairTryAcquire方法來第二次嘗試獲得鎖
- 如果state變量為0陪蜻,則進行CAS嘗試更新state來獲得鎖,并把該線程設置成獨占鎖贱鼻,并返回true宴卖。
- 如果state變量不為0,則判斷當前線程是否為獨占鎖忱嘹,如果是嘱腥,則當前state+1(可重入鎖),表示獲取鎖成功拘悦,更新state值齿兔,并返回true。這里更新state變量础米,不需要CAS更新分苇,因為,當前線程已經(jīng)獲得鎖屁桑。
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;
}
將構(gòu)造的同步節(jié)點加入到同步隊列中
- 使用鏈表的方式把該Node節(jié)點添加到隊列尾部医寿,如果tail的前驅(qū)節(jié)點不為空(隊列不為空),則進行CAS添加到隊列尾部蘑斧。
- 如果更新失斁钢取(存在并發(fā)競爭更新)须眷,則進入enq方法進行添加
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;
}
}
}
}
該方法使用CAS自旋的方式來保證向隊列中添加Node(同步節(jié)點簡寫Node)
- 如果隊列為空,則把當前Node設置成頭節(jié)點
- 如果隊列不為空沟突,則向隊列尾部添加Node
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);
}
}
在acquireQueued方法中花颗,當前線程在死循環(huán)中嘗試獲取同步狀態(tài),
- 如果當前節(jié)點的前驅(qū)節(jié)點頭節(jié)點才能嘗試獲得鎖惠拭,如果獲得成功扩劝,則把當前線程設置成頭結(jié)點,把之前的頭結(jié)點從隊列中移除职辅,等待垃圾回收(沒有對象引用)
- 如果獲取鎖失敗則進入shouldParkAfterFailedAcquire方法中檢測當前節(jié)點是否可以被安全的掛起(阻塞)棒呛,如果可以安全掛起則進入parkAndCheckInterrupt方法,把當前線程掛起,并檢查剛線程是否執(zhí)行了interrupted方法域携。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 嘗試將當前節(jié)點的前驅(qū)節(jié)點的等待狀態(tài)設為SIGNAL
* 1/這為什么用CAS簇秒,現(xiàn)在已經(jīng)入隊成功了,前驅(qū)節(jié)點就是pred涵亏,除了node外應該沒有別的線程在操作這個節(jié)點了宰睡,那為什么還要用CAS?而不直接賦值呢气筋?
* (解釋:因為pred可以自己將自己的狀態(tài)改為cancel,也就是pred的狀態(tài)可能同時會有兩條線程(pred和node)去操作)
* 2/既然前驅(qū)節(jié)點已經(jīng)設為SIGNAL了旋圆,為什么最后還要返回false
* (因為CAS可能會失敗宠默,這里不管失敗與否,都返回false灵巧,下一次執(zhí)行該方法的之后搀矫,pred的等待狀態(tài)就是SIGNAL了)
* (網(wǎng)上摘抄的,解釋的很明白)
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
waitStatus狀態(tài)值
狀態(tài) | 值 | 說明 |
---|---|---|
CANCELLED | 1 | 等待超時或者中斷刻肄,需要從同步隊列中取消 |
SIGNAL | -1 | 后繼節(jié)點出于等待狀態(tài)瓤球,當前節(jié)點釋放鎖后將會喚醒后繼節(jié)點 |
CONDITION | -2 | 節(jié)點在等待隊列中,節(jié)點線程等待在Condition上敏弃,其它線程對Condition調(diào)用signal()方法后卦羡,該節(jié)點將會從等待同步隊列中移到同步隊列中,然后等待獲取鎖麦到。 |
PROPAGATE | -3 | 表示下一次共享式同步狀態(tài)獲取將會無條件地傳播下去 |
INITIAL | 0 | 初始狀態(tài) |
- 首先獲取前驅(qū)節(jié)點的等待狀態(tài)ws
- 如果ws為SIGNAL則表示可以被前驅(qū)節(jié)點喚醒绿饵,當前線程就可以掛起,等待前驅(qū)節(jié)點喚醒瓶颠,返回true(可以掛起)
- 如果ws>0說明拟赊,前驅(qū)節(jié)點取消了,并循環(huán)查找此前驅(qū)節(jié)點之前所有連續(xù)取消的節(jié)點粹淋。并返回false(不能掛起)吸祟。
- 嘗試將當前節(jié)點的前驅(qū)節(jié)點的等待狀態(tài)設為SIGNAL
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
把當前線程掛起,并檢查剛線程是否執(zhí)行了interrupted方法瑟慈,并返回true、false屋匕。
公平鎖
公平鎖和非公平鎖實現(xiàn)方式是一樣的葛碧,唯一不同的是tryAcquire方法的實現(xiàn),下面是公平鎖tryAcquire方法實現(xiàn):
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;
}
- 首先獲取當前鎖狀態(tài)炒瘟,如果當前state==0(無鎖)吹埠,則進行獲取鎖操作
- hasQueuedPredecessors方法判斷頭結(jié)點是否當前線程,如果是當前線程則進行CAS更新獲得鎖疮装,獲取成功缘琅,把當前線程設置成獨占鎖。
- 如果不是頭結(jié)點或獲取鎖失敗則廓推,則判斷當前線程是否為獨占鎖刷袍,如果是,則當前state+1(可重入鎖)樊展,表示獲取鎖成功呻纹,更新state值,并返回true专缠。這里更新state變量雷酪,不需要CAS更新,因為涝婉,當前線程已經(jīng)獲得鎖哥力。
想了解更多精彩內(nèi)容請關注我的公眾號