Java中的ReentrantReadWriteLock實現原理詳解
介紹
讀寫鎖
- 實現了接口ReadWriteLock
- 適合于讀多寫少的情況
- 支持公平鎖和非公平鎖
- 支持可沖入(進入讀鎖后可再進入讀鎖,進入寫鎖后可再進入寫鎖和讀鎖)
- 支持可沖入和公平與非公平
缺點
(1) 寫鎖饑餓問題,讀的線程很多,寫的線程搶占不到鎖,就一直搶占不到鎖,就饑餓
(2) 鎖降級,獲取寫鎖后又再次獲取讀鎖(重入),釋放了寫鎖之后就變成了讀鎖,就是鎖降級
內部接口
Sync/ReadLock/WriteLock/FairSync/NonfairSync 是其內部類 下面圖有問題,ReadWriteLock沒有實現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 + "結束");
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 + "結束");
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毫秒,演示讀鎖沒有完成之前,寫鎖無法獲得
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(); }
// 在讀鎖沒有完成時, 寫鎖不能寫入
for (int i = 1; i <=3; i++) {
int finalI = i;
new Thread(() -> {
myResource.write(finalI +"", finalI +"");
},"新寫鎖線程->"+String.valueOf(i)).start();
}
}
}
實現原理
從表面來看,ReadLock和WriteLock是兩把鎖,實際上它只是同一把鎖的兩個視圖而已。
什么叫兩個視圖呢?可以理解為是一把鎖,線程分成兩類:讀線程和寫線程。讀線程和寫線程之間不互斥(可以同時拿到這把鎖),讀線程之間不互斥,寫線程之間互斥
從下面的構造方法也可以看出,readerLock和writerLock實際共用同一個sync對象。
sync對象同互斥鎖一樣,分為非公平和公平兩種策略,并繼承自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變量來表示鎖狀態(tài)的。只是state變量在這里的含義和互斥鎖完全不同。在內部類Sync中,對state變量進行了重新定義,如下所示:
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;
// 持有讀鎖的線程的重入次數
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 持有寫鎖的線程的重入次數
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
// ...
}把 state變量拆成兩半,低16位用來記錄寫鎖。但同一時間既然只能有一個線程寫,為什么還需要16位呢?這是因為一個寫線程可能多次重入。
例如,低16位的值等于5,表示一個寫線程重入了5次 高16位,用來讀鎖。
如高16位的值等于5,既可以表示5個讀線程都拿到了該鎖;也可以表示一個讀線程重入了5次
為什么要把一個int類型變量拆成兩半,而不是用兩個int型變量分別表示讀鎖和寫鎖的狀態(tài)呢?因為無法用一次CAS同時操作兩個int變量,所以用了一個int型的高16位和低16位分別表示讀鎖和寫鎖的狀態(tài)
當state=0時,說明既沒有線程持有讀鎖,也沒有線程持有寫鎖;當state != 0時,要么有線程持有讀鎖,要么有線程持有寫鎖,兩者不能同時成立,因為讀和寫互斥。
這時再進一步通過sharedCount(state)和exclusiveCount(state)判斷到底是讀線程還是寫線程持有了該鎖
到此這篇關于Java中的ReentrantReadWriteLock實現原理詳解的文章就介紹到這了,更多相關ReentrantReadWriteLock原理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
microlog4android將Android Log日志寫到SD卡文件中實現方法
這篇文章主要介紹了microlog4android將Android Log日志寫到SD卡文件中實現方法的相關資料,需要的朋友可以參考下2016-10-10
Spring實現在非controller中獲取request對象
這篇文章主要介紹了Spring實現在非controller中獲取request對象方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
mybatis-xml映射文件及mybatis動態(tài)sql詳解
XML映射文件的名稱與Mapper接口名稱一致,并且將XML映射文件和Mapper接口放置在相同包下(同包同名),這篇文章主要介紹了mybatis-xml映射文件及mybatis動態(tài)sql的相關知識,感興趣的朋友跟隨小編一起看看吧2024-12-12
Java8 Stream中對集合數據進行快速匹配和賦值的代碼示例
這篇文章主要介紹了Java8 Stream中如何對集合數據進行快速匹配和賦值,文中通過代碼示例為大家介紹的非常詳細,具有一定的參考價值,需要的朋友可以參考下2023-06-06

