一、死磕Java——ReentrantLock
ReentrantLock
是java.util.concurrent.locks
包下一個可重入的預設是非公平的鎖,ReentrantLock
類是Lock
介面的一個使用很頻繁的實現類,類結構如下圖:
前面說過JMM
模型要求的是可見性,原子性和有序性。解決原子性的方法也有多種,例如synchronized
同步方法或者同步程式碼塊,也可以使用AtomicInteger
原子包裝類解決,都知道synchronized
加鎖是最笨重的解決方法,所裡,這裡使用的是ReentrantLock
加鎖來實現原子性,程式碼如下:
class MyData {
int num = 0;
Lock lock = new ReentrantLock();
public void add() {
try {
lock.lock();
num++;
} finally {
lock.unlock();
}
}
}
public class ReentrantLockDemo {
private static Logger log = LoggerFactory.getLogger(ReentrantLockDemo.class);
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
myData.add();
}
}, String.valueOf(i)).start();
}
// 知道所有的執行緒執行結束
while (Thread.activeCount() > 1) {
Thread.yield();
}
log.info("結果:{}", myData.num);
}
}
複製程式碼
1.1.ReentrantLock的公平性
ReentrantLock
提供了一個帶引數的建構函式,來讓使用者決定使用是否是公平鎖。
通過原始碼我們可以知道,無引數就是預設為非公平鎖,傳入true
表示公平鎖,傳入false
表示非公平鎖,原始碼如下
// 空參預設為非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
複製程式碼
// 根絕引數決定鎖的公平性
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
複製程式碼
1.2.ReentrantLock的非公平鎖
1.2.1使用ReentrantLock加鎖
// 建立一個非公平鎖
Lock lock = new ReentrantLock();
try {
// 加鎖
lock.lock();
} finally {
// 釋放鎖
lock.unlock();
}
複製程式碼
1.2.2.執行的是ReentrantLock的lock方法
public void lock() {
sync.lock();
}
複製程式碼
1.2.3.呼叫的是NonfairSync類的lock方法
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
// 通過CAS思想去AQS佇列中獲取將State值從0變為1,即獲取到鎖
if (compareAndSetState(0, 1))
// 獲取鎖成功則將當前執行緒標記為持有鎖的執行緒,然後直接返回
setExclusiveOwnerThread(Thread.currentThread());
else
// 獲取鎖失敗則執行該方法
acquire(1);
}
}
複製程式碼
1.2.4.呼叫AQS的cAS方法獲取鎖
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
// unsafe類是通過native方法直接操作記憶體資料
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
複製程式碼
1.2.5.獲取鎖失敗執行acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
複製程式碼
1.2.6.tryAcquire嘗試獲取鎖
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
複製程式碼
tryAcquire
這個方法是AQS
預設的鉤子方法,不同類的有不同的實現,其中NonfairSync
實現如下:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
複製程式碼
final boolean nonfairTryAcquire(int acquires) {
// 傳入的acquires為1,獲取當前執行緒
final Thread current = Thread.currentThread();
// 獲取state變數的值,即當前鎖被重入的次數
int c = getState();
// state為0,說明當前鎖未被任何執行緒持有
if (c == 0) {
// 通過CAS思想去AQS佇列中獲取將State值從0變為1,即獲取到鎖
if (compareAndSetState(0, acquires)) {
// 獲取鎖成功則將當前執行緒標記為持有鎖的執行緒,然後直接返回
setExclusiveOwnerThread(current);
// 返回嘗試獲取鎖成功
return true;
}
}
// //當前執行緒就是持有鎖的執行緒,說明該鎖被重入了
else if (current == getExclusiveOwnerThread()) {
// //計算state變數要更新的值
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// //非同步方式更新state值
setState(nextc);
// 獲取鎖成功,返回結果
return true;
}
// 嘗試獲取鎖失敗
return false;
}
複製程式碼
1.2.7.獲取鎖失敗後將執行緒加入到同步佇列中
private Node addWaiter(Node mode) {
// 創造一個新的節點,傳入的mode引數為null
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
// tail是指向佇列尾元素的尾指標,新節點的頭指標指向佇列的尾指標
Node pred = tail;
// //佇列不為空
if (pred != null) {
// 新節點的頭指標修改為佇列的尾指標
node.prev = pred;
// 使用CAS演算法,如果記憶體中的佇列還是之前的尾指標就把新節點指向尾指標
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 安全的加入同步佇列
enq(node);
return node;
}
複製程式碼
1.2.8.將執行緒加入同步佇列
private Node enq(final Node node) {
for (;;) {
// t節點指向當前佇列的最後一個節點
Node t = tail;
// 佇列為空
if (t == null) { // Must initialize
// 通過CAS構造新節點
if (compareAndSetHead(new Node()))
// 尾指標指向新節點
tail = head;
} else {
// 佇列不為空時候,將節點的頭指標指向佇列的尾指標
node.prev = t;
// 使用CAS演算法,如果記憶體中的佇列還是之前的尾指標就把新節點指向尾指標
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
複製程式碼
當佇列為空的時候通過CAS更新頭節點原始碼如下:
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
複製程式碼
說明:僅當佇列中原值為null時更新成功。
當佇列不為空的時候通過CAS更新尾節點原始碼如下:
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
複製程式碼
說明:CAS方式更新tial指標,僅當原值為t時更新成功
1.2.9.執行緒進入佇列後
final boolean acquireQueued(final Node node, int arg) {
// 引數arg為1,節點是為獲取到鎖的執行緒節點
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);
}
}
複製程式碼
1.3.ReentrantLock加鎖總結
1.4.ReentrantLock非公平解鎖
lock.unlock();
複製程式碼
1.4.1.釋放鎖
public void unlock() {
sync.release(1);
}
複製程式碼
public final boolean release(int arg) {
// 釋放鎖(state-1),若釋放後鎖可被其他執行緒獲取(state=0),返回true
if (tryRelease(arg)) {
// 獲取佇列的頭節點
Node h = head;
//當前佇列不為空且頭結點狀態不為初始化狀態(0)
if (h != null && h.waitStatus != 0)
// 喚醒同步佇列中被阻塞的執行緒
unparkSuccessor(h);
return true;
}
return false;
}
複製程式碼
1.4.2.嘗試釋放鎖
protected final boolean tryRelease(int releases) {
// 計算待更新的state值
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 待更新的state值為0,說明持有鎖的執行緒未重入,一旦釋放鎖其他執行緒將能獲取
if (c == 0) {
free = true;
// 清除鎖的持有執行緒標記
setExclusiveOwnerThread(null);
}
// 更新的state值
setState(c);
return free;
}
複製程式碼
我們可以看見一個很重要的抽象類AbstractQueuedSynchronizer
,有關AQS
稍後在死磕,可以說AQS
是同步元件的基礎。