一、為什么要出現讀寫鎖
synchronized和ReentrantLock都是互斥鎖。
如果說有一個操作是讀多寫少的,還要保證線程安全的話。如果采用上述的兩種互斥鎖,效率方面很定是很低的。
在這種情況下,咱們就可以使用ReentrantReadWriteLock讀寫鎖去實現。
讀讀之間是不互斥的,可以讀和讀操作并發執行。
但是如果涉及到了寫操作,那么還得是互斥的操作。
static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
static ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
readLock.lock();
try {
System.out.println("子線程!");
try {
Thread.sleep(500000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
readLock.unlock();
}
}).start();Thread.sleep(1000);
writeLock.lock();
try {
System.out.println("主線程!");
} finally {
writeLock.unlock();
}
}
二、讀寫鎖的實現原理
ReentrantReadWriteLock還是基于AQS實現的,還是對state進行操作,拿到鎖資源就去干活,如果沒有拿到,依然去AQS隊列中排隊。
讀鎖操作:基于state的高16位進行操作。
寫鎖操作:基于state的低16為進行操作。
ReentrantReadWriteLock依然是可重入鎖。
寫鎖重入:讀寫鎖中的寫鎖的重入方式,基本和ReentrantLock一致,沒有什么區別,依然是state進行+1操作即可,只要確認持有鎖資源的線程,是當前寫鎖線程即可。只不過之前ReentrantLock的重入次數是state的正數取值范圍,但是讀寫鎖中寫鎖范圍就變小了。
讀鎖重入:因為讀鎖是共享鎖。讀鎖在獲取鎖資源操作時,是要對state的高16位進行 + 1操作。因為讀鎖是共享鎖,所以同一時間會有多個讀線程持有讀鎖資源。這樣一來,多個讀操作在持有讀鎖時,無法確認每個線程讀鎖重入的次數。為了去記錄讀鎖重入的次數,每個讀操作的線程,都會有一個ThreadLocal記錄鎖重入的次數。
寫鎖的饑餓問題:讀鎖是共享鎖,當有線程持有讀鎖資源時,再來一個線程想要獲取讀鎖,直接對state修改即可。在讀鎖資源先被占用后,來了一個寫鎖資源,此時,大量的需要獲取讀鎖的線程來請求鎖資源,如果可以繞過寫鎖,直接拿資源,會造成寫鎖長時間無法獲取到寫鎖資源。
讀鎖在拿到鎖資源后,如果再有讀線程需要獲取讀鎖資源,需要去AQS隊列排隊。如果隊列的前面需要寫鎖資源的線程,那么后續讀線程是無法拿到鎖資源的。持有讀鎖的線程,只會讓寫鎖線程之前的讀線程拿到鎖資源。
三、寫鎖分析
3.1 寫鎖加鎖流程概述
3.2 寫鎖加鎖源碼分析
寫鎖加鎖流程
// 寫鎖加鎖的入口
public void lock() {
sync.acquire(1);
}
// 阿巴阿巴!!
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}
// 讀寫鎖的寫鎖實現tryAcquire
protected final boolean tryAcquire(int acquires) {
// 拿到當前線程
Thread current = Thread.currentThread();
// 拿到state的值
int c = getState();
// 得到state低16位的值
int w = exclusiveCount(c);
// 判斷是否有線程持有著鎖資源
if (c != 0) {
// 當前沒有線程持有寫鎖,讀寫互斥,告辭。
// 有線程持有寫鎖,持有寫鎖的線程不是當前線程,不是鎖重入,告辭。
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 當前線程持有寫鎖。 鎖重入。
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 沒有超過鎖重入的次數,正常 + 1
setState(c + acquires);
return true;
}
// 嘗試獲取鎖資源
if (writerShouldBlock() ||
// CAS拿鎖!compareAndSetState(c, c + acquires))
return false;
// 拿鎖成功,設置占有互斥鎖的線程
setExclusiveOwnerThread(current);
// 返回true
return true;
}
// ================================================================
// 這個方法是將state的低16位的值拿到
int w = exclusiveCount(c);
state & ((1 << 16) - 1)
00000000 00000000 00000000 00000001 == 1
00000000 00000001 00000000 00000000 == 1 << 16
00000000 00000000 11111111 11111111 == (1 << 16) - 1
&運算,一個為0,必然為0,都為1,才為1
// ================================================================
// writerShouldBlock方法查看公平鎖和非公平鎖的效果
// 非公平鎖直接返回false執行CAS嘗試獲取鎖資源
// 公平鎖需要查看是否有排隊的,如果有排隊的,我是否是head的next
3.3 寫鎖釋放鎖流程概述&釋放鎖源碼
釋放的流程和ReentrantLock一致,只是在判斷釋放是否干凈時,判斷低16位的值
// 寫鎖釋放鎖的tryRelease方法
protected final boolean tryRelease(int releases) {
// 判斷當前持有寫鎖的線程是否是當前線程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 獲取state - 1
int nextc = getState() - releases;
// 判斷低16位結果是否為0,如果為0,free設置為true
boolean free = exclusiveCount(nextc) == 0;
if (free)
// 將持有鎖的線程設置為null
setExclusiveOwnerThread(null);
// 設置給state
setState(nextc);
// 釋放干凈,返回true。 寫鎖有沖入,這里需要返回false,不去釋放排隊的Node
return free;
}