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(); }
}
}
}