JDK1.5開始就出現了java.util.concurrent.locks.ReentrantReadWriteLock,將鎖細分程了讀鎖和寫鎖,
- 讀鎖:屬於共享鎖,多個線程可同時獲取該鎖,在讀鎖代碼中獲取不到寫鎖,只有讀鎖釋放後才能獲取到寫鎖
- 寫鎖:屬於獨享鎖,只有一個線程能獲取到該鎖並操作數據,在寫鎖中還可以獲取到讀鎖(寫鎖的降級)
可以同通過這兩種鎖做一些緩存工具-代碼如下:
package com.milla.study.netbase.expert.concurrent.lock;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Package: com.milla.study.netbase.expert.concurrent.lock
* @Description: <>
* @Author: MILLA
* @CreateDate: 2020/6/1 14:19
* @UpdateUser: MILLA
* @UpdateDate: 2020/6/1 14:19
* @UpdateRemark: <>
* @Version: 1.0
*/
public class CacheDataTests {
//讀寫鎖
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
//讀鎖
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
//寫鎖
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
//緩存數據
Map<String, Object> data = new HashMap<>();
static final ThreadPoolExecutor executor = new
ThreadPoolExecutor(8, 12, 10,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(30), new ThreadPoolExecutor.CallerRunsPolicy());//如果線程池沒有關閉直接運行任務(性能不高)
public static void main(String[] args) {
CacheDataTests cacheData = new CacheDataTests();
for (int i = 0; i < 1000; i++) {
Double random = Math.random() * 10;
int key = random.intValue();
executor.submit(() -> System.out.println(cacheData.cache(key + "")));
}
executor.shutdown();
}
//-從緩存中獲取數據,如果不存在及從數據庫或者其他地方獲取數據,當前爲模擬自動生成數據
public Object cache(String key) {
try {
//加讀鎖-讀鎖屬於共享鎖可以支持多個線程獲取鎖,但此時排斥寫鎖
readLock.lock();
//如果有對應的key
if (Objects.isNull(data.get(key))) {
//解除讀鎖
readLock.unlock();
//添加寫鎖,保證寫鎖內操作只有一個線程能執行-避免該處過多線程操作導致崩潰或數據不一致
writeLock.lock();
try {
//雙重判斷,防止有其他線程已經處理,出現重複處理的情況
if (Objects.isNull(data.get(key))) {
//獲取數據 TODO 需要進行的具體操作
String s = UUID.randomUUID().toString();
System.out.println("key: " + key + " value: " + s);
data.put(key, s);
}
//在沒釋放寫鎖前,加讀鎖,將其他線程的可能進行的寫鎖屏蔽-降級寫鎖處理,保證數據的一致性
readLock.lock();
} finally {
//最終釋放寫鎖-此時仍然存在讀鎖
writeLock.unlock();
}
}
return data.get(key);
} finally {
//最終釋放讀鎖
readLock.unlock();
}
}
}
PS:不得不感嘆大神們確實是奇思妙想,很多年前就給出了自己的獨到見解。 工具類直接參照JDK,僞代碼如下:
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// Recheck state because another thread might have
// acquired write lock and changed state before we did.
if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // Unlock write, still hold read
}
}
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
}
//也可以定義一個字典類
class RWDictionary {
private final Map<String, Data> m = new TreeMap<String, Data>();
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
public Data get(String key) {
r.lock();
try { return m.get(key); }
finally { r.unlock(); }
}
public String[] allKeys() {
r.lock();
try { return m.keySet().toArray(); }
finally { r.unlock(); }
}
public Data put(String key, Data value) {
w.lock();
try { return m.put(key, value); }
finally { w.unlock(); }
}
public void clear() {
w.lock();
try { m.clear(); }
finally { w.unlock(); }
}
}
}