命题:首先实现一个简单的缓存,主要功能是获取getConfig、定时刷新。
思路:
(1)采用ScheduledThreadPoolExecutor异步定时刷新
(2)采用ConcurrentHashMap作为缓存数据结构,保证并发写的线程安全,并采用computeIfAbsent实现单个key的懒加载——如果没有就到库里获取。
public class CacheDemo {
private static Map<String,Object> cache = new ConcurrentHashMap<>();
static {
int twoAM = 2 * 60 * 60 * 1000;
long delayTo2AM = twoAM - System.currentTimeMillis() % (24 * 60 * 60 * 1000);
new ScheduledThreadPoolExecutor(1).scheduleAtFixedRate(CacheDemo::refreshAll,delayTo2AM, 24, TimeUnit.HOURS);
}
public static Object getConfig(String key){
return cache.computeIfAbsent(key, CacheDemo::loadFromDB);
}
private static Object loadFromDB(String key){
return null;
}
private static void refreshAll(){
Map<String, Object> loadAll = loadAllFromDB();
cache.clear();
cache.putAll(loadAll);
}
public static Map<String,Object> loadAllFromDB(){
return null;
}
}
如果有其他线程正在执行 getConfig 方法读取缓存,就可能会读到正在被清空或更新的缓存,导致读取到的数据不准确.
解决方案1,在 refreshAll 方法中对 cache 对象加锁,确保在刷新缓存的过程中,其他线程不能读取或写入缓存。但这样做会降低系统的并发性能。
方案2,使用 ReadWriteLock,在读取缓存时获取读锁,在刷新缓存时获取写锁。这样可以确保在刷新缓存的过程中,其他线程不能读取或写入缓存,但在不刷新缓存的时候,多个线程可以同时读取缓存,提高了系统的并发性能。
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class CacheDemo {
private static final Map<String,Object> cache = new ConcurrentHashMap<>();
private static final ReadWriteLock lock = new ReentrantReadWriteLock();
static {
int twoAM = 2 * 60 * 60 * 1000;
long delayTo2AM = twoAM - System.currentTimeMillis() % (24 * 60 * 60 * 1000);
new ScheduledThreadPoolExecutor(1).scheduleAtFixedRate(CacheDemo::refreshAll, delayTo2AM, 24, TimeUnit.HOURS);
}
//在读取缓存的时候,其他线程可以同时读取缓存,但不能写入缓存。
public static Object getConfig(String key){
lock.readLock().lock();
try {
return cache.computeIfAbsent(key, CacheDemo::loadFromDB);
} finally {
lock.readLock().unlock();
}
}
private static Object loadFromDB(String key){
return null;
}
private static void refreshAll(){
lock.writeLock().lock();
try {
Map<String, Object> loadAll = loadAllFromDB();
cache.clear();
cache.putAll(loadAll);
} finally {
lock.writeLock().unlock();
}
}
public static Map<String,Object> loadAllFromDB(){
return null;
}
}