可重入讀寫(xiě)鎖 ReentrantReadWriteLock
1. 前言
從本節(jié)開(kāi)始,我們學(xué)習(xí)新一章內(nèi)容 —— 并發(fā)鎖。
在 “Java 并發(fā)原理入門教程” 中,介紹了鎖相關(guān)概念和原理知識(shí),本章各小節(jié)內(nèi)容不再過(guò)多解釋概念,重點(diǎn)為大家介紹具體鎖工具的 API 和使用方法。
本節(jié)帶領(lǐng)大家認(rèn)識(shí)第一個(gè)常用的 Java 并發(fā)鎖工具之 ReentrantReadWriteLock。
本節(jié)先簡(jiǎn)單介紹 ReentrantReadWriteLock 的基本概念,然后介紹關(guān)鍵的編程方法,最后通過(guò)一個(gè)編程例子為大家展示 ReentrantReadWriteLock 工具類的用法。
下面我們正式開(kāi)始介紹吧。
2. 概念解釋
ReadWriteLock 是一個(gè)接口類型,翻譯為 “讀寫(xiě)鎖”。多個(gè)線程同時(shí)讀某個(gè)資源時(shí),為了滿足并發(fā)量,不應(yīng)該加鎖處理,但是如果有一個(gè)線程寫(xiě)這個(gè)資源,就不應(yīng)該再有其它線程對(duì)該資源進(jìn)行讀操作或者寫(xiě)操作。
實(shí)現(xiàn)了此接口的類提供了 “讀讀能共存、讀寫(xiě)不能共存、寫(xiě)寫(xiě)不能共存” 的控制邏輯。當(dāng)同一個(gè)資源被大量線程讀取,但僅有少數(shù)線程修改時(shí),使用 ReadWriteLock 可大大提高并發(fā)效率。比如對(duì)一個(gè)電商網(wǎng)站的商品,回復(fù)評(píng)論(寫(xiě))是不頻繁的,但是瀏覽(讀)是非常頻繁的,這種情況使用 ReadWriteLock 工具類做并發(fā)控制非常適合??傊m合讀多寫(xiě)少的場(chǎng)景。
ReentrantLock 翻譯為 “可重入鎖”?!翱芍厝搿?是什么意思呢?就是指一個(gè)線程可以多次獲取該鎖,Java 中 在語(yǔ)言語(yǔ)法層提供的 syncrinized 是最常見(jiàn)的用于并發(fā)控制的關(guān)鍵字,其構(gòu)成的鎖也是可重入的??芍厝腈i在一定程度可以避免死鎖的發(fā)生。
更多關(guān)于鎖的原理,可閱讀 “Java 并發(fā)原理入門教程”。
ReentrantReadWriteLock 類實(shí)現(xiàn)了 “讀寫(xiě)鎖” 和 “可重入鎖” 的雙重功能。其本身不提供加鎖服務(wù),只負(fù)責(zé)提供讀鎖和寫(xiě)鎖。在介紹關(guān)鍵編程方法之前,我們先看一張圖整體了解關(guān)鍵方法的使用方式。
下面我們學(xué)習(xí)其關(guān)鍵的編程方法。
3.ReentrantReadWriteLock 的編程方法
-
構(gòu)造方法 ReentrantReadWriteLock () 和 ReentrantReadWriteLock (boolean fair)
有兩個(gè)構(gòu)造方法,在構(gòu)造對(duì)象時(shí),可選擇是否構(gòu)造為公平鎖模式。什么是公平鎖呢?公平鎖指的是獲取所的線程按照申請(qǐng)鎖的線程獲取,而非亂序隨機(jī)獲取到鎖權(quán)限。 -
writeLock () 和 readLock ()
這兩個(gè)方法分別用于獲取寫(xiě)鎖和獲取讀鎖,對(duì)讀操作的邏輯使用讀鎖對(duì)象加鎖,對(duì)寫(xiě)操作的邏輯使用寫(xiě)鎖對(duì)象加鎖,如此可以做到 “讀讀能共存、讀寫(xiě)不能共存、寫(xiě)寫(xiě)不能共存”,在實(shí)現(xiàn)線程安全的情況下,提高并發(fā)效率。 -
isFair()
獲取鎖是否具有公平性,此方法返回 boolean 值。 -
getWriteHoldCount () 、 getReadHoldCount () 和 getReadLockCount ()
getWriteHoldCount () 方法用于獲取當(dāng)前線程的寫(xiě)鎖計(jì)數(shù),getReadHoldCount () 方法用于獲取當(dāng)前線程的讀鎖計(jì)數(shù),getReadLockCount () 方法用于獲取總的讀鎖計(jì)數(shù)。所有方法均返回 int 值。
4.ReentrantReadWriteLock.ReadLock 的編程方法
此類是一個(gè)靜態(tài)內(nèi)部類,封裝在 ReentrantReadWriteLock 中,此類不對(duì)外提供構(gòu)造方法,由 ReentrantReadWriteLock.readLock () 方法獲取此類對(duì)象。
-
lock () 和 lockInterruptibly ()
兩個(gè)方法都用于加讀鎖,第二個(gè)方法可被中斷。 -
tryLock () 和 tryLock (long timeout, TimeUnit unit)
兩個(gè)方法都返回 boolean 值,用于嘗試加讀鎖,第一個(gè)方法在嘗試不成功時(shí)立刻返回,第二個(gè)方法可在指定時(shí)間內(nèi)嘗試加讀鎖。這兩個(gè)方法提供了加鎖的柔性,提供了更多操作空間。 -
unlock()
釋放讀鎖。
5.ReentrantReadWriteLock.WriteLock 的編程方法
此類是一個(gè)靜態(tài)內(nèi)部類,封裝在 ReentrantReadWriteLock 中,此類不對(duì)外提供構(gòu)造方法,由 ReentrantReadWriteLock.writeLock () 方法獲取此類對(duì)象。
-
lock () 和 lockInterruptibly ()
兩個(gè)方法都用于加寫(xiě)鎖,第二個(gè)方法可被中斷。 -
tryLock () 和 tryLock (long timeout, TimeUnit unit)
兩個(gè)方法都返回 boolean 值,用于嘗試加寫(xiě)鎖,第一個(gè)方法在嘗試不成功時(shí)立刻返回,第二個(gè)方法可在指定時(shí)間內(nèi)嘗試加寫(xiě)鎖。這兩個(gè)方法提供了加鎖的柔性,提供了更多操作空間。 -
unlock()
釋放寫(xiě)鎖。
6. 編程案例
上面介紹了核心編程方法,我們舉一個(gè)編程案例,實(shí)際體會(huì)一下 ReentrantReadWriteLock 的用法。
import lombok.SneakyThrows;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
// 創(chuàng)建讀寫(xiě)鎖對(duì)象
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 獲取讀鎖對(duì)象
private final Lock readlock = readWriteLock.readLock();
// 獲取寫(xiě)鎖對(duì)象
private final Lock writelock = readWriteLock.writeLock();
// 待控制的資源
private int account = 0;
private static ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();
public static void main(String[] args) {
new Thread(new Runnable() {
@SneakyThrows
public void run() {
while(true) {
Thread.sleep(1000);
int tmp = readWriteLockTest.get();
System.out.println("讀操作:" + tmp);
}
}
}).start();
new Thread(new Runnable() {
@SneakyThrows
public void run() {
while(true) {
Thread.sleep(2000);
readWriteLockTest.add(10);
}
}
}).start();
}
public void add(int value) {
// 加寫(xiě)鎖
writelock.lock();
try {
account += 1;
} finally {
// 釋放寫(xiě)鎖
writelock.unlock();
}
}
public int get() {
// 加讀鎖
readlock.lock();
try {
return account;
} finally {
// 釋放讀鎖
readlock.unlock();
}
}
}
運(yùn)行上面代碼一段時(shí)間后結(jié)果如下:
讀操作:0
讀操作:1
讀操作:1
讀操作:2
讀操作:2
注意在使用時(shí),獲取鎖的操作 lock () 應(yīng)該放在 try 之前,而釋放鎖的操作 unlock () 需要放在 finally 中,可確保鎖釋放。
7. 小結(jié)
本節(jié)解釋了 ReentrantReadWriteLock 的基本概念和應(yīng)用場(chǎng)合,且通過(guò)一個(gè)簡(jiǎn)單的例子展示了其用法,更多關(guān)于此工具類的概念和原理介紹,可閱讀 “Java 并發(fā)原理入門教程” 。希望大家在學(xué)習(xí)過(guò)程中,多思考勤練習(xí),早日掌握之。