构建java高效的缓存

在项目的开发中,如果出现一些数据量较大且需要频繁读取而又很少改动场景时,基本上会考虑使用缓存,以降低数据库的压力,毕竟访问到数据库的数据是相当耗费资源。

说到缓存,大家可能更多先想到使用HashMap存储,代码如下:
public Map<String, Object> cache = new HashMap<String, Object>();
public Object getData(String key){
	Object data = cache.get(key);
	if (data == null) {
		data = DB.getData();	//从数据库中获取数据(伪代码)
		cache.put(key, data);
	}
	return data;
}
这样就不用每次访问页面都去读数据库的数据,提高了性能。
但是代码有缺陷:HashMap不是线程安全,所以多线程并发的时候会出现问题,所以需要加锁同步,代码如下:
public Map<String, Object> cache = new HashMap<String, Object>();
public synchronized Object getData(String key){
	Object data = cache.get(key);
	if (data == null) {
		data = DB.getData();	//从数据库中获取数据(伪代码)
		cache.put(key, data);
	}
	return data;
}
这样就解决了线程安全的问题。
但是加了锁之后,每次只能进去一个线程,尽管是不同缓存数据,还是要等待上一个线程执行完才能执行,性能出现问题。

我们可以尝试使用ConcurrentHashMap来代替HashMap,由于ConcurrentHashMap是线程安全,所以代码不需要加同步锁,并且使用了分段的原理,比HashMap有更好的解决并发操作。

public Map<String, Object> cache = new ConcurrentHashMap<String, Object>();  
public Object getData(String key){  
    Object data = cache.get(key);  
    if (data == null) {  
        data = DB.getData();    //从数据库中获取数据(伪代码)  
        cache.put(key, data);  
    }  
    return data;  
}

这样的代码还是有问题,就是第一次初始化缓存是如果需要经历开销很大的计算(例如需要耗费10s),那么在这10s内访问同一个缓存的线程都需要进行重新初始化缓存的操作,这样既浪费时间又浪费资源。


我们可以使用一下Callable接口、Future接口、FutureTask类来实现,Callable是一个线程任务接口,Future是有关任务处理的接口,Future.get会获取任务的状态,如果任务已经完成,则能返回结果,否则get将阻塞直到任务
 进入完成状态,然后返回结果或抛出异常FutureTask是Future的实现类。

public Map<String, Future<Object>> cache = new ConcurrentHashMap<String, Future<Object>>();
public Object getData(String key) throws InterruptedException { 
	Future<Object> future = cache.get(key);
	if (future == null) {
		Callable<Object> call = new Callable<Object>() {
			@Override
			public Object call() throws Exception {
				return DB.getData();
			}
		}; 
		FutureTask<Object> task = new FutureTask<Object>(call);
		future = task;
		cache.put(key, future);
		task.run();		//这里再从数据库获取数据
	}
	try {
		return future.get();	
	} catch (ExecutionException e) {
		//处理执行任务时出现异常
	}
}

这样就能解决多次初始化同一个缓存的问题,如果是任务还未执行完,下一个请求就阻塞,直到任务执行完再返回数据,有效节省资源。
但是代码还是存在问题,例如在初始化Future任务时的并发问题,并且执行到一半时任务出现中断的情况。
改进代码如下:
public ConcurrentHashMap<String, Future<Object>> cache = new ConcurrentHashMap<String, Future<Object>>();  
public Object getData(String key) throws InterruptedException {   
	while (true) {  
		Future<Object> future = cache.get(key);  
        if (future == null) {  
            Callable<Object> call = new Callable<Object>(){  
                @Override  
                public Object call() throws Exception {  
                    return DB.getData();  
                }  
            };  
            FutureTask<Object> task = new FutureTask<Object>(call);  
            cache.putIfAbsent(key, task);  
            if (future == null) {  
                future = task;  
                task.run();  
            }  
        }  
        try {  
            return future.get();  
        } catch (CancellationException e) {  
            cache.remove(key, future);  //任务出现取消时的处理  
        } catch (ExecutionException e) {  
             //处理执行任务时出现异常  
        }  
    }  
}



这样代码就解决了初始化Future任务并发的问题,还解决了任务出现取消操作时候的处理,putIfAbsent有效的解决将相同的任务重复加到Map中(注意这是ConcurrentHashMap的方法,需要使用ConcurrentHashMap去定义对象),避免了上一个版本的漏洞。
这样,缓存最终实现。



大家看完还有什么不懂或是觉得代码哪里有问题,欢迎留言交流。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值