多線程之讀寫鎖

前言
在java中,鎖lock是多線程編程的一個(gè)重要組件,可以說凡是涉及到多線程編程,線程安全這一塊就無法避開lock,進(jìn)一步說就是所有的線程安全都是基于鎖實(shí)現(xiàn)的,只是從形式上分為隱式鎖和顯式鎖,synchronized就屬于隱式鎖,像我們之前分享的可重入鎖就屬于顯式鎖,當(dāng)然顯示鎖還有很多,我們今天就來看一個(gè)很常用的顯式鎖——讀寫鎖。
讀寫鎖
讀寫鎖是一對互斥鎖,分為讀鎖和寫鎖。讀鎖和寫鎖互斥,讓一個(gè)線程在進(jìn)行讀操作時(shí),不允許其他線程的寫操作,但是不影響其他線程的讀操作;當(dāng)一個(gè)線程在進(jìn)行寫操作時(shí),不允許任何線程進(jìn)行讀操作或者寫操作。
簡單來說就是,寫鎖會(huì)排斥讀和寫,但是讀鎖只排斥寫,這樣的好處就很明顯,在讀多寫少的應(yīng)用場景下,比其他互斥鎖性能要好很多。下面我們通過一段代碼說明:
public class Example {
private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) {
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
ExecutorService executorService = Executors.newCachedThreadPool();
// 寫操作1:寫鎖
executorService.execute(() -> {
writeLock.lock();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("寫count1: " + count.incrementAndGet());
}
writeLock.unlock();
});
// 讀操作1:讀鎖
executorService.execute(() -> {
readLock.lock();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("讀count1: " + count.get());
}
readLock.unlock();
});
// 讀操作2:讀鎖
executorService.execute(() -> {
readLock.lock();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("讀count2: " + count.get());
}
readLock.unlock();
});
// 寫操作2:寫鎖
executorService.execute(() -> {
writeLock.lock();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("寫count2: " + count.incrementAndGet());
}
writeLock.unlock();
});
// 寫操作3:寫鎖
executorService.execute(() -> {
writeLock.lock();
for (int i = 0; i < 5; i++) {
System.out.println("寫count3: " + count.incrementAndGet());
}
writeLock.unlock();
});
executorService.shutdown();
}
}
上面的代碼中,我們分別有3次寫操作,2次讀操作,然后運(yùn)行上面的代碼:

運(yùn)行結(jié)果圖片上我們已經(jīng)標(biāo)注了線程休眠情況,根據(jù)運(yùn)行結(jié)果雖然寫count1休眠了1000,讀count1休眠了500,但因?yàn)閷戞i的存在,排斥了其他讀寫操作,讀只能等寫鎖釋放,所以是先寫后讀,也就是寫鎖排斥讀;
讀count2休眠了200,讀count1休眠了500,雖然加了讀鎖,但結(jié)果還是讀count2先運(yùn)行,而且期間是讀count1和讀count2交替運(yùn)行,說明讀并不排斥讀,而且讀后面的寫count2只休眠了100,但還是在讀count1和讀count2之后運(yùn)行,說明讀排斥寫;
寫count2休眠100,寫count3未休眠,但是寫count3依然在寫count2之后執(zhí)行,說明寫鎖排斥寫。
綜上,我們在示例中分別證明了我們上面的結(jié)論:寫鎖會(huì)排斥讀和寫,但是讀鎖只排斥寫。
總結(jié)
讀寫鎖相比于其他互斥鎖,優(yōu)點(diǎn)很明顯,也就是前面我們說的:在讀多寫少的應(yīng)用場景下,比其他互斥鎖性能要好很多。至于原因,我們也通過示例證明了:寫鎖會(huì)排斥讀和寫,但是讀鎖只排斥寫。
關(guān)于讀寫鎖,我覺得今天分享的內(nèi)容已經(jīng)比較詳細(xì),不僅展示了它的基本用法,同時(shí)還通過示例反證了它的互斥原則,所以如果你看明白了今天的內(nèi)容,那么互斥鎖這一款的知識你就掌握了。好了,今天的內(nèi)容就到這里吧!
- END -