概述
在編碼中常使用ReentrantLock時候启上,它可以實現(xiàn)線程在獲取鎖時候公平與非公平。所謂公平在排隊者挨個獲取鎖捏萍,非公平排隊者第一個可能和插隊者爭搶鎖。我們想來上一個類圖了解他們之間關(guān)系旷余。
源碼分析
我們先從ReentrantLock構(gòu)造方法開始分析。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
從代碼可以看出扁达,默認采用非公平鎖正卧。接下來了解,公平鎖和非公平鎖他們代碼具體差異在哪里跪解。
//公平鎖
static final class FairSync extends Sync {
final void lock() {
//1參數(shù)為1 表示要去獲得鎖
acquire(1);
}
//非公平鎖
static final class NonfairSync extends Sync {
final void lock() {
//2 通過CAS直接加鎖炉旷,如果鎖未被他人使用,
if (compareAndSetState(0, 1))
//3設(shè)置當前執(zhí)行的線程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
}
- 注釋1叉讥,公平鎖窘行,直接獨占方式獲取鎖。
- 注釋2图仓,非公平鎖罐盔,通過CAS直接加鎖,如果鎖未被他人使用救崔,修改成功獲得鎖惶看。如果沒有獲取到鎖然后調(diào)用 acquire(1)方法。
- 注釋3六孵,設(shè)置當前執(zhí)行的線程纬黎。
我們繼續(xù)看上面類是如何嘗試獲取鎖的。
//公平鎖
static final class FairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//1 此方法保證獲得鎖根據(jù)隊列來 先來先獲得鎖
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;
}
}
//非公平鎖
static final class NonfairSync extends Sync {
//2 嘗試在去獲得鎖
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
// 嘗試獲取鎖
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//3 嘗試獲取鎖劫窒,如果鎖未被使用本今。先進入的線程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//4 如果鎖被自己占用,自己獲取鎖可重入鎖
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;
}
}
//5 判斷頭結(jié)點是否是當前線程
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer{
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
}
- 注釋1主巍,公平鎖冠息,hasQueuedPredecessors方法判斷頭結(jié)點(AQS后面講,線程轉(zhuǎn)換成雙向鏈表才存儲)是否是當前線程煤禽。如果是(按照鏈表順序執(zhí)行)當前線程铐达,直接嘗試獲取鎖。
- 注釋2檬果,非公平鎖瓮孙,嘗試獲取鎖
- 注釋3,發(fā)起線程嘗試獲取鎖选脊,如果鎖未被使用則獲取到搜杭抠,這里和鏈表的head存在競爭關(guān)系,鎖被爭搶恳啥。
- 注釋4偏灿,如果鎖被自己占用,自己獲取鎖可重入鎖钝的。
- 注釋5翁垂,判斷頭結(jié)點是否是當前線程铆遭。
接下來我們來分析AQS(AbstractQueuedSynchronizer)源碼,不管是公平鎖還是非公平鎖沿猜,以下實現(xiàn)機制都一樣枚荣。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
嘗試獲取鎖,如果沒有獲取到鎖啼肩,把當前線程封裝成Node節(jié)點橄妆,存入鏈表中。
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** 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;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
把線程封裝成Node祈坠,且有自己的狀態(tài)waitStatus害碾。
//1 為獲取鎖的線程,添加等待節(jié)點
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
// 2 tail 尾結(jié)點
Node pred = tail;
// 尾結(jié)點不為空
if (pred != null) {
//3 在尾結(jié)點添加新節(jié)點
node.prev = pred;
//4 修改尾結(jié)點的內(nèi)存地址內(nèi)存赦拘,tail = node
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
// 創(chuàng)建tail節(jié)點內(nèi)容慌随,第一個節(jié)點為哨兵節(jié)點,第二個節(jié)點為node
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//5 尾結(jié)點為空另绩,創(chuàng)建哨兵節(jié)點(Node都是一個空對象)
if (t == null) { // Must initialize
//6 tail head 都為哨兵節(jié)點
if (compareAndSetHead(new Node()))
tail = head;
} else {
//7 為node 指定 prev節(jié)點儒陨,首先初始化哨兵節(jié)點花嘶,然后把哨兵節(jié)點作為node的prev笋籽。
node.prev = t;
//8 尾結(jié)點變化成 node
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- 注釋1,為獲取鎖的線程椭员,添加等待節(jié)點车海。
- 注釋2,拿到尾結(jié)點隘击。
- 注釋3侍芝,如果pre節(jié)點不為空,添加新節(jié)點到尾結(jié)點處埋同。
- 注釋4州叠,修改尾結(jié)點的內(nèi)存地址內(nèi)存的值,tail = node凶赁。
- 注釋5咧栗,尾結(jié)點為空,創(chuàng)建哨兵節(jié)點(Node都是一個空對象)虱肄。
- 注釋6致板,給tail和head 賦值哨兵節(jié)點。
- 注釋7咏窿,為node 指定 prev節(jié)點斟或,首先初始化哨兵節(jié)點,然后把哨兵節(jié)點作為node的prev集嵌。
- 注釋8萝挤,尾結(jié)點變化成 node御毅。
總結(jié),上面兩個方法主要做的事情是怜珍,創(chuàng)建哨兵節(jié)點亚享,下一個節(jié)點為node。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//1 獲得node的上一個節(jié)點
final Node p = node.predecessor();
//2 p節(jié)點是頭結(jié)點绘面,通過調(diào)用tryAcquire方法獲得鎖
if (p == head && tryAcquire(arg)) {
//3 把node節(jié)點變成哨兵節(jié)點
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//4 Node狀態(tài)變成等待欺税,刪除線程取消的節(jié)點。parkAndCheckInterrupt等待獲取鎖
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// 進入阻塞狀態(tài)
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//5 等待獲取鎖狀態(tài)
if (ws == Node.SIGNAL)
return true;
// 6 線程被取消
if (ws > 0) {
do {
//7 刪除被取消的線程節(jié)點
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
- 注釋1揭璃,獲得node(新線程)的上一個節(jié)點晚凿。
- 注釋2,p節(jié)點是頭結(jié)點瘦馍,通過調(diào)用tryAcquire方法獲得鎖歼秽。
- 注釋3,把node節(jié)點變成哨兵節(jié)點情组。
- 注釋4燥筷,Node狀態(tài)變成等待,刪除線程取消的節(jié)點院崇。parkAndCheckInterrupt等待獲取鎖肆氓。
- 注釋5,等待獲取鎖狀態(tài)底瓣。
- 注釋6谢揪,被標識為取消的節(jié)點。
- 注釋7捐凭,刪除被取消的線程節(jié)點拨扶。
我們了解到通過AQS給需要加鎖的線程,封裝成Node雙向鏈表茁肠。接下來我們看看如何喚醒阻塞的線程患民。
//ReentrantLock.java
// 釋放鎖
public void unlock() {
sync.release(1);
}
//AbstractQueuedSynchronizer.java
public final boolean release(int arg) {
if (tryRelease(arg)) {
//1 獲取得到頭結(jié)點
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//2 拿到頭結(jié)點下一個節(jié)點,都是等待獲取鎖的節(jié)點
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;
}
// 給節(jié)點s垦梆,發(fā)一張允許票
if (s != null)
LockSupport.unpark(s.thread);
}
- 注釋1匹颤,獲取得到頭結(jié)點。
- 注釋2奶赔,拿到頭結(jié)點下一個節(jié)點惋嚎,都是等待獲取鎖的節(jié)點。
以上是所有線程使用ReentrantLock原理站刑,如有描述錯誤地方請大家多多指正另伍。