用Callable和CurrentHashMap实现排它锁

在构建缓存的时候,不可避免的要使用排它锁,防止多个线程同时检测到没有缓存,而去查询数据库。此时一般情况下都会使用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来做缓存,功能非常强大,操作也很简单。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值