ReentrantLock是依賴CAS機制寫的。
CAS全名Compare And Swap,比較如果相同就交換,全名更容易記住,并且具有意義。
CAS在ReentrantLock中的實作,樣例1:
//同步器狀态,如果是0,同步器中沒有線程在運作,也就是lock釋放狀态
//如果是1,則代表同步器中有線程在運作,lock沒有被釋放
private volatile int state;
//擷取state變量在目前對象所占記憶體裡的偏移量(就是state的位置)
private static final long stateOffset;
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
//調用這個方法來更新state值
//expect:期望值
//update:更新值
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
變量一定要volatile修飾,保證變量的可見性。對volatile的簡要說明:ReentrantLock實作就是用來保證多線程的并發資料統一性的安全控制,在多線程下,要修改一個公共變量,可能會因為更新不及時其他線程導緻髒讀,而volatile就是能保證各個線程能及時看到操作後最新的值。(volatile的實作原理涉及比較底層了)。
votaile修飾完變量,為了在多線程情況下,能原子性的進行更新操作。這裡的原子性操作隻有兩步,首先比較值,然後更新。
拿上面的這行代碼unsafe.compareAndSwapInt(this, stateOffset, expect, update);來說,this代表目前對象,stateOffset代表變量的位置,expect代表此變量我們期望的值,update代表此變量将要更新的值。實際場景:如果此變量現在的值和我們預期的一樣,那麼就更新值。
了解完CAS,再來講一下ReentrantLock大概結構:
ReentrantLock它繼承了AbstractQueuedSynchronizer抽象類,當你看到ReentrantLock是依賴CAS和AQS的時候,不要疑惑AQS是啥,就是AbstractQueuedSynchronizer,AQS是一個很重要的類。java中實作的很多鎖的重要實作都依賴這個類。
AQS的實作大概思路:想象一下在多線程環境下,我怎麼控制它們一個個執行呢。首先,我們要把這些要執行的線程給存儲起來,這個存儲器就叫它同步器吧。AQS采用的是雙向連結清單結構去存儲。定義一個Node類,主要變量包括Thread和mode,Thread當然是線程,mode是模式類别(獨占式和共享式),ReentrantLock就是獨占式模式,因為鎖一次隻能被一個線程擷取。存儲好了,那麼如何控制這些線程一個個運作呢?ReentrantLock的控制就是根據上面這個state變量來實作的。
我們來嘗試解讀一下ReentrantLock中的非公平鎖裡的lock()方法:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
首先判斷compareAndSetState(0, 1)是否執行成功,執行成功的條件就是期望state=0,代表這時候同步器裡沒有線程在運作,鎖是空閑的。我們把state設定為1了,代表鎖被占用了,其他的線程執行lock()方法時就會被挂起。擷取到了鎖,再執行setExclusiveOwnerThread(Thread.currentThread());這個是将目前線程給變量指派操作,存儲是哪個線程擷取到了鎖。如果沒有成功則執行acquire(1)。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
這裡tryAcquire(arg)方法仍然嘗試去擷取鎖,如果不成功,再往下執行。先執行addWaiter(Node.EXCLUSIVE),這個是将目前線程設定為獨占式節點插入到同步器的末端(将目前線程存儲起來)。再執行acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法
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);
}
}
可以看到這裡是一個for(;;)無限循環,一直嘗試去擷取鎖,那麼這不是死循環了嗎。parkAndCheckInterrupt()會調動Unsafe的unPark(false,0L)方法,相當于sleep(0),讓目前線程放棄cpu,讓cpu可以去做其他任務,這樣完成一個為搶占鎖不斷阻塞的程式。關于sleep(0),這篇文章寫的不錯:https://blog.csdn.net/qiaoquan3/article/details/56281092/
具體情形還請自尋檢視源碼。ReentrantLock的unlock()方法比它lock()方法簡單多了。