Java实现一个线程安全的本地缓存

372 阅读2分钟

命题:首先实现一个简单的缓存,主要功能是获取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;
    }

}