在构建缓存的时候,不可避免的要使用排它锁,防止多个线程同时检测到没有缓存,而去查询数据库。此时一般情况下都会使用synchronized或者ReentrantLock来实现。今天给大家介绍一种比较巧妙的实现方式:使用Callable和CurrentHashMap来实现。
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.FutureTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 构建缓存时,增加排它锁,避免多个线程对同一个cacheKey同时访问数据库
* @author lizhiyang
*
*/
public class TaskUtils {
private TaskUtils() {}
private static final Logger logger = LoggerFactory.getLogger(TaskUtils.class);
/**
* 任务缓存map,key是加锁的键值,value需要加锁的代码(用线程来实现)
*/
private static final ConcurrentMap<String, FutureTask<Object>> taskHolder =
new ConcurrentHashMap<String, FutureTask<Object>>();
private static ExecutorService executorService = new ThreadPoolExecutor(2, 4, 5, TimeUnit.MINUTES,
new ArrayBlockingQueue<>(500));
/**
* 确保整个jvm只有一个任务在执行
* @param cacheKey
* @param caller
* @return
*/
@SuppressWarnings("unchecked")
public static <E> E getInTask(String cacheKey, Callable<E> caller) {
// 建立运行的任务
FutureTask<Object> ft = new FutureTask<Object>((Callable<Object>) caller);
// 检查缓存是否有缓存在运行
FutureTask<Object> t = taskHolder.putIfAbsent(cacheKey,(FutureTask<Object>) ft);
if (t == null) {
// 计算结果
executorService.submit(ft);
t = ft;
}
try {
E result = (E) t.get();
return result;
} catch (InterruptedException ie) {
logger.warn("Task was interrupted. msg=" + ie.getMessage());
} catch (Exception e) {
logger.error("getInTask error", e);
} finally {
taskHolder.remove(cacheKey, t);
}
// 发生异常时,返回null
return null;
}
}
使用方式如下:
使用:
//创建Callable,执行数据库查询操作
Callable<List<Long>> caller = new Callable<List<Long>>() {
public List<Long> call() throws InterruptedException {
//查询数据库之前再次判断缓存中是否存在值
List<Long> goodsIdList = spyMemClient.get(cacheKey);
if (goodsIdList != null) {
LOG.info(new JsonLog("public rec by cache").put("cacheKey", cacheKey).toString());
return goodsIdList;
}
if(cacheProxy.containsDbNotHit(cacheKey)) {
LOG.debug("public rec by cache contains db not hit : "+cacheKey);
return Collections.emptyList();
}
//缓存中不存在相应值,查询数据库
Map<String, String> parameters = new HashMap<String, String>();
parameters.put("city", strCityName);
parameters.put("amount", String.valueOf(amount));
List<String> goodsIdStrList = mobileService.getPublicRecList(parameters);
goodsIdList = dcodeGoodsId(goodsIdStrList);
if(goodsIdList == null || goodsIdList.isEmpty()) {
cacheProxy.setDbNotHit(cacheKey);
LOG.debug("public rec by db set db not hit : "+cacheKey);
} else {
cacheProxy.cacheRecResult(cacheKey, goodsIdList);
LOG.info(new JsonLog("public rec by db").put("cacheKey", cacheKey).toString());
}
return goodsIdList;
}
};
//真实调用
goodsIdList = TaskUtils.getInTask(cacheKey, caller);
另外:在做缓存的时候,最好能够把缓存的时间进行离散化处理,否则会造成大量缓存在同一时间点失效,同时去访问数据库,瞬间数据库压力剧增。一般情况下,可以使用Memcached和Redis来做缓存,功能非常强大,操作也很简单。