Java中的ReentrantReadWriteLock實(shí)現(xiàn)原理詳解
介紹
讀寫鎖
- 實(shí)現(xiàn)了接口ReadWriteLock
- 適合于讀多寫少的情況
- 支持公平鎖和非公平鎖
- 支持可沖入(進(jìn)入讀鎖后可再進(jìn)入讀鎖,進(jìn)入寫鎖后可再進(jìn)入寫鎖和讀鎖)
- 支持可沖入和公平與非公平
缺點(diǎn)
(1) 寫鎖饑餓問(wèn)題,讀的線程很多,寫的線程搶占不到鎖,就一直搶占不到鎖,就饑餓
(2) 鎖降級(jí),獲取寫鎖后又再次獲取讀鎖(重入),釋放了寫鎖之后就變成了讀鎖,就是鎖降級(jí)
內(nèi)部接口
Sync/ReadLock/WriteLock/FairSync/NonfairSync 是其內(nèi)部類 下面圖有問(wèn)題,ReadWriteLock沒有實(shí)現(xiàn)Lock

代碼演示
public class Lock {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Lock.put(i + "", i + "");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Lock.get(i + "");
}
}
}).start();
}
static Map<String, Object> map = new HashMap<String, Object>();
static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
static java.util.concurrent.locks.Lock readLock = readWriteLock.readLock();
static java.util.concurrent.locks.Lock writeLock = readWriteLock.writeLock();
public static final Object get(String key) {
readLock.lock();
try {
System.out.println("正在做讀的操作,key:" + key + "開始");
Thread.sleep(100);
Object object = map.get(key);
System.out.println("正在做讀的操作,key:" + key + "結(jié)束");
System.out.println();
return object;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
return key;
}
public static final Object put(String key, Object value) {
writeLock.lock();
try {
System.out.println("正在做寫的操作,key:" + key + ",value:" + value + "開始");
Thread.sleep(100);
Object object = map.put(key, value);
System.out.println("正在做寫的操作,key:" + key + ",value:" + value + "結(jié)束");
System.out.println();
return object;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
return value;
}
public static final void clear() {
writeLock.lock();
try {
map.clear();
} finally {
writeLock.unlock();
}
}
}class MyResource {
Map<String,String> map = new HashMap<>();
Lock lock = new ReentrantLock();
ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void write(String key ,String value) {
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"正在寫入");
map.put(key,value);
//暫停毫秒
try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t"+"完成寫入");
}finally {
rwLock.writeLock().unlock();
}
}
public void read(String key) {
rwLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"正在讀取");
String result = map.get(key);
// 暫停2000毫秒,演示讀鎖沒有完成之前,寫鎖無(wú)法獲得
try { TimeUnit.MILLISECONDS.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t"+"完成讀取"+"\t"+result);
}finally {
rwLock.readLock().unlock();
}
}
}
/**
* @auther zzyy
* @create 2022-04-08 18:18
*/
public class ReentrantReadWriteLockDemo {
public static void main(String[] args) {
MyResource myResource = new MyResource();
for (int i = 1; i <=10; i++) {
int finalI = i;
new Thread(() -> {
myResource.write(finalI +"", finalI +"");
}, String.valueOf(i)).start();
}
for (int i = 1; i <=10; i++) {
int finalI = i;
new Thread(() -> {
myResource.read(finalI +"");
},String.valueOf(i)).start();
}
// 暫停幾秒鐘線程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
// 在讀鎖沒有完成時(shí), 寫鎖不能寫入
for (int i = 1; i <=3; i++) {
int finalI = i;
new Thread(() -> {
myResource.write(finalI +"", finalI +"");
},"新寫鎖線程->"+String.valueOf(i)).start();
}
}
}
實(shí)現(xiàn)原理
從表面來(lái)看,ReadLock和WriteLock是兩把鎖,實(shí)際上它只是同一把鎖的兩個(gè)視圖而已。
什么叫兩個(gè)視圖呢?可以理解為是一把鎖,線程分成兩類:讀線程和寫線程。讀線程和寫線程之間不互斥(可以同時(shí)拿到這把鎖),讀線程之間不互斥,寫線程之間互斥
從下面的構(gòu)造方法也可以看出,readerLock和writerLock實(shí)際共用同一個(gè)sync對(duì)象。
sync對(duì)象同互斥鎖一樣,分為非公平和公平兩種策略,并繼承自AQS
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync(); // fair為false使用非公平鎖,true使用公平鎖
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
同互斥鎖一樣,讀寫鎖也是用state變量來(lái)表示鎖狀態(tài)的。只是state變量在這里的含義和互斥鎖完全不同。在內(nèi)部類Sync中,對(duì)state變量進(jìn)行了重新定義,如下所示:
abstract static class Sync extends AbstractQueuedSynchronizer {
// ...
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 持有讀鎖的線程的重入次數(shù)
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 持有寫鎖的線程的重入次數(shù)
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
// ...
}把 state變量拆成兩半,低16位用來(lái)記錄寫鎖。但同一時(shí)間既然只能有一個(gè)線程寫,為什么還需要16位呢?這是因?yàn)橐粋€(gè)寫線程可能多次重入。
例如,低16位的值等于5,表示一個(gè)寫線程重入了5次 高16位,用來(lái)讀鎖。
如高16位的值等于5,既可以表示5個(gè)讀線程都拿到了該鎖;也可以表示一個(gè)讀線程重入了5次
為什么要把一個(gè)int類型變量拆成兩半,而不是用兩個(gè)int型變量分別表示讀鎖和寫鎖的狀態(tài)呢?因?yàn)闊o(wú)法用一次CAS同時(shí)操作兩個(gè)int變量,所以用了一個(gè)int型的高16位和低16位分別表示讀鎖和寫鎖的狀態(tài)
當(dāng)state=0時(shí),說(shuō)明既沒有線程持有讀鎖,也沒有線程持有寫鎖;當(dāng)state != 0時(shí),要么有線程持有讀鎖,要么有線程持有寫鎖,兩者不能同時(shí)成立,因?yàn)樽x和寫互斥。
這時(shí)再進(jìn)一步通過(guò)sharedCount(state)和exclusiveCount(state)判斷到底是讀線程還是寫線程持有了該鎖
到此這篇關(guān)于Java中的ReentrantReadWriteLock實(shí)現(xiàn)原理詳解的文章就介紹到這了,更多相關(guān)ReentrantReadWriteLock原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一篇文章學(xué)會(huì)java死鎖與CPU 100%的排查
這篇文章主要介紹了一篇文章學(xué)會(huì)java死鎖與CPU 100%的排查,文中主要介紹了Java死鎖以及服務(wù)器CPU占用率達(dá)到100%時(shí)的排查和解決方法,感興趣的朋友一起來(lái)看一看吧2021-08-08
microlog4android將Android Log日志寫到SD卡文件中實(shí)現(xiàn)方法
這篇文章主要介紹了microlog4android將Android Log日志寫到SD卡文件中實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2016-10-10
Spring實(shí)現(xiàn)在非controller中獲取request對(duì)象
這篇文章主要介紹了Spring實(shí)現(xiàn)在非controller中獲取request對(duì)象方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Spring Boot自定義Banner實(shí)現(xiàn)代碼
這篇文章主要介紹了Spring Boot自定義Banner實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01
mybatis-xml映射文件及mybatis動(dòng)態(tài)sql詳解
XML映射文件的名稱與Mapper接口名稱一致,并且將XML映射文件和Mapper接口放置在相同包下(同包同名),這篇文章主要介紹了mybatis-xml映射文件及mybatis動(dòng)態(tài)sql的相關(guān)知識(shí),感興趣的朋友跟隨小編一起看看吧2024-12-12
SpringBoot部署在tomcat容器中運(yùn)行的部署方法
這篇文章主要介紹了SpringBoot部署在tomcat容器中運(yùn)行的部署方法,需要的朋友可以參考下2018-10-10
Java8 Stream中對(duì)集合數(shù)據(jù)進(jìn)行快速匹配和賦值的代碼示例
這篇文章主要介紹了Java8 Stream中如何對(duì)集合數(shù)據(jù)進(jìn)行快速匹配和賦值,文中通過(guò)代碼示例為大家介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2023-06-06
Spring七大事務(wù)傳遞機(jī)制深入分析實(shí)現(xiàn)原理
實(shí)際項(xiàng)目開發(fā)中,如果涉及到多張表操作時(shí),為了保證業(yè)務(wù)數(shù)據(jù)的一致性,大家一般都會(huì)采用事務(wù)機(jī)制,好多小伙伴可能只是簡(jiǎn)單了解一下,遇到事務(wù)失效的情況,便會(huì)無(wú)從下手,下面這篇文章主要給大家介紹了關(guān)于Spring事務(wù)傳遞機(jī)制的相關(guān)資料,需要的朋友可以參考下2023-03-03
MybatisPlus搭建項(xiàng)目環(huán)境及分頁(yè)插件
Mybatis-Plus(簡(jiǎn)稱MP)是一個(gè)Mybatis的增強(qiáng)工具,在Mybatis的基礎(chǔ)上只做增強(qiáng)不做改變,為簡(jiǎn)化開發(fā)、提高效率而生,下面這篇文章主要給大家介紹了關(guān)于MybatisPlus搭建項(xiàng)目環(huán)境及分頁(yè)插件的相關(guān)資料,需要的朋友可以參考下2022-11-11

