ReentrantLock源碼
- synchronized通過在對象頭的markword進行操作從而實現互斥鎖
- ReentrantLock通過將線程加入AQS阻塞隊列從而實現同步互斥鎖
首先初始化一個ReentrantLock
ReentrantLock lock = new ReentrantLock(true);
這個時候是默認的構造函數,新建一個公平鎖
public ReentrantLock(boolean fair){
sync = fair ? new FairSync() : new NonfairSync();
}
然後加鎖操作lock.lock()
,這個方法會去調用FairSync下面的lock()
方法
final void lock(){
acquire(1);
}
接着我們進入這個acquire(1)
去看看,原來它是AQS的一個方法
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先第一個判斷是tryAcquire(arg)
,我們應該去它的子類,也就是Sync
去看,這個時候也就是去FairSync
類的tryAcquire(arg)
去看
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;
}
AQS源碼(自旋+CAS+park/unpark)
AQS(AbstractQueuedSynchronizer)類的設計主要構成
// 隊首
private transient volatile Node head;
// 隊尾
private transient volatile Node tail;
// 鎖狀態
private volatile int state;
// 持有鎖的那個線程(這個屬性其實是在AbstractOwnableSynchronizer類裏的)
private transient Thread exclusiveOwnerThread;
AQS中的入隊操作源碼解析
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使第一個線程入隊
enq(node);
return node;
}
enq
// 通過這個函數我們可以看出AQS中的head一直都是指向一個空的Node節點
private Node enq(final Node node) {
// 若當前隊列爲空,那麼for循環會執行兩次,第一次執行if(將head指向一個空的Node),第二次執行else(將當前的node連接到上個空Node之後,也就是入隊)
for (;;) {
Node t = tail;
// 第一次會執行if(將head指向一個空的Node)
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
}
// 第二次會執行else(將當前的node連接到上個空Node之後,也就是入隊)
else {
// 連接到上一個空Node,即入隊
node.prev = t;
// 將隊列尾節點指向當前節點node
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
入隊後的隊列結構如下圖所示:注意,隊列頭爲空節點,代表的是當前獲得鎖的線程。也可以理解爲持有鎖的線程永遠不會在隊列裏(保證持有鎖的線程不參與排隊的原則),比如這個時候t1拿到鎖了,那麼t1會被置位head,並且裏面的值全爲null(見acquireQueued方法中的setHead方法)。
多線程非交替執行下。比如t1線程獲取了ReentrantLock鎖,這個時候t2線程來了,對t2的處理流程圖如下圖所示:
總結
ReentrantLock
比synchronized
快,是因爲前者的加鎖操作是在JDK的工作層次下操作的,而後者需要調用操作系統去工作。
ReentrantLock
中一部分操作是在JDK的層面下解決的,還有一部分也是在操作系統中解決的;- 多線程交替執行(不會交叉,比如線程t1執行完了t2纔開始執行,這個時候和隊列沒有關係):比如上面貼出的AQS中的
acquire
方法,首先是執行tryAcquire
這個方法是不會調用操作系統的,如果這個方法返回true了,那就不會再繼續判斷後面調用操作系統的的acquireQueued
方法 - 多線程不是交替執行的:這個時候就會AQS中的隊列就不爲空了。
- 多線程交替執行(不會交叉,比如線程t1執行完了t2纔開始執行,這個時候和隊列沒有關係):比如上面貼出的AQS中的
synchronized
在JDK1.6之前全都要經過操作系統來解決,所以那個時候提出了ReentrantLock
ReentrantLock.lockTnterruptibly()
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;