1、什麼是鎖
A lock is a tool for controlling access to a shared resource by
multiple threads. Commonly, a lock provides exclusive access to a
shared resource引用自JDK的java.util.concurrent.locks.Lock接口說明
鎖即是對共享資源的獨佔訪問的一種機制,當然也有共享資源的鎖,如ReadWriteLock中的Read鎖。
2、聊聊J.U.C中的鎖
JUC中的locks包,包含了多種鎖,如可重入鎖、讀寫鎖及帶戳的版本鎖等等,下面我們看一下這幾個鎖的用途及區別。
2.1 ReentrantLock
ReentrantLock
提供了公平和非公平鎖,默認非公平。
分別由FairSync
NonfairSync
實現,其父類爲Sync
,而Sync
父類爲AbstractQueuedSynchronizer
,AQS的核心代碼實現了ReentrantLock的鎖的邏輯。
這一章節從源碼角度說明公平和非公平鎖的區別以及AQS是如何實現lock和unlock邏輯的:
java.util.concurrent.locks.ReentrantLock#lock
public void lock() {
// 調用lock,根據構造的是FairSync還是NonfairSync,調用其lock方法
sync.lock();
}
// 先看FairSync.lock();
final void lock() {
// AQS的acquire()
acquire(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
// 這裏分爲3步:
// 第一步:tryAcquire(arg),如果成功獲取到鎖,則lock()方法取鎖完成,否則繼續下一步
// 第二步:addWaiter(),構造Node並加入到隊尾(AQS包含了head、tail指針,用於記錄等待獲取鎖的線程隊列)
// 第三步:acquireQueued(),獲取隊列裏的Node,判斷其是否真正獲取到鎖,如果沒有獲取到鎖且需要被打斷則執行selfInterrupt()將當前線程打斷
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// state是AQS中的成員變量,記錄鎖的狀態,大於0說明鎖被某個線程鎖住了
int c = getState();
// state == 0, 說明鎖沒有被佔
if (c == 0) {
// 判斷前面是否還要線程再等待(公平鎖就是要等前面先排隊的)
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果當前線程已經加過鎖了,則把state再往上加,這裏就有可重入鎖的味道
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// 非公平鎖調用的加鎖代碼是其父類Sync的nonfairTryAcquire方法
// java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 看到鎖沒被其他線程佔住,二話不說直接搶佔
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 這裏和公平鎖一樣的邏輯, state往上加
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;
}
以上就是ReenTrantLock的加鎖邏輯。
下面分析下unlock釋放鎖:
java.util.concurrent.locks.ReentrantLock#unlock
public void unlock() {
sync.release(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#release
public final boolean release(int arg) {
// 先釋放鎖
if (tryRelease(arg)) {
// AQS中的head指針,head不爲空,說明加鎖等待隊列不爲空,需要喚醒head指針中記錄的thread,並將其喚醒
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// java.util.concurrent.locks.ReentrantLock.Sync#tryRelease
// 釋放鎖其實就是修改state的值,如果爲0,說明鎖釋放成功
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 這裏判斷了釋放鎖的線程和持有鎖的線程是不是同一個線程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
2.2 ReentrantReadWriteLock
我們知道,給共享資源加鎖導致了線程獨佔資源,目的是爲了保護共享資源的正確性。但線程訪問資源,有可能只是去讀取數據,而不會修改數據,這時候其他線程獨佔共享資源,導致讀操作要一直等待,降低了系統性能。
讀寫鎖就是爲了解決這個問題:讀鎖和寫鎖的加鎖關係如下
能否同時加鎖 | 讀 | 寫 |
---|---|---|
讀 | Y | N |
寫 | N | N |
只有多個線程都是讀操作,才能共同加“讀”鎖。其他情況下,只有一個線程能加鎖成功。
2.3 StampedLock
JDK1.8及以後版本提供了帶時間戳的鎖,包含了讀寫鎖的功能。這是一個帶版本號的鎖,每次給對象加鎖,會返回一個stamp,之後的釋放鎖需要用到這個stamp,如果和鎖當前的stamp不匹配則釋放鎖失敗。