用Callable和CurrentHashMap实现排它锁

来源:互联网 发布:2013年旅游数据统计 编辑:程序博客网 时间:2024/05/16 05:05
在构建缓存的时候,不可避免的要使用排它锁,防止多个线程同时检测到没有缓存,而去查询数据库。此时一般情况下都会使用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>>();   /**    * 确保整个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) {// 计算结果ft.run();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);}// 发生异常时,返回nullreturn 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来做缓存,功能非常强大,操作也很简单。

0 0
原创粉丝点击