一种习以为常的缓存写法:

IF value in cached THEN
return value from cache
ELSE
compute value
save value in cache
return value
END IF

看上去逻辑无比正确,但实际上会造成2种问题:

1、这种方法是不线程安全的。

2、产生数值写入重复,造成错误的数据。

如下图,在线程1执行计算数值的过程中,线程2也进入数据检查,将多次写入数据,程序非常危险。

演示错误代码:

    //最容易产生的错误写法,先读取缓存,读不到就写缓存
public Long getNumber(final long index) {
if (cache.containsKey(index)) {
return cache.get(index);
} final long value = getNumber(index - ) + getNumber(index - );
cache.put(index, value);
return value;
}

1、传统的解决办法,使用重入锁 (getNumberByLock 方法)或者同步锁(getNumberBySynchroniz 方法)。

代码

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class NaiveCacheExample { private final Map<Long, Long> cache = new HashMap<>();
private Object o=new Object();
Lock lock =new ReentrantLock(); public NaiveCacheExample() {
cache.put(0L, 1L);
cache.put(1L, 1L);
} //最容易产生的错误写法,先读取缓存,读不到就写缓存
public Long getNumber(final long index) {
if (cache.containsKey(index)) {
return cache.get(index);
} final long value = getNumber(index - ) + getNumber(index - );
cache.put(index, value);
return value;
} //使用折返锁,使读写同步
public Long getNumberByLock(final long index) {
long value =;
try {
lock.lock();
if (cache.containsKey(index)) {
return cache.get(index);
}
value = getNumberByLock(index - ) + getNumberByLock(index - );
cache.put(index, value);
return value;
}
catch (Exception e)
{}
finally
{
lock.unlock();
} return 0l;
} //使用同步,使读写同步
public Long getNumberBySynchroniz(final long index) {
synchronized (o)
{
long value =;
try {
if (cache.containsKey(index)) {
return cache.get(index);
}
value = getNumberBySynchroniz(index - ) + getNumberBySynchroniz(index - );
cache.put(index, value);
return value;
}
catch (Exception e)
{}
finally
{ }
}
return 0l;
} public static void main(final String[] args) { NaiveCacheExample naiveCacheExample =new NaiveCacheExample(); Thread threadA =new Thread(new Runnable()
{
@Override
public void run() {
System.out.println(naiveCacheExample.getNumberBySynchroniz());
} }
,"Thread-A");
threadA.start(); final Thread threadB = new Thread(new Runnable() {
public void run() {
System.out.println(naiveCacheExample.getNumberBySynchroniz());
}
}, "Thread-B");
threadB.start(); }
}

2、一个更好的缓存算法可以用 Callable 和 Future 。 缓存的值将存储在一个实例 ConcurrentMap 中 ,ConcurrentMap 是线程安全的。

代码:

import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask; public class GenericCacheExample<K, V> { private final ConcurrentMap<K, Future<V>> cache = new ConcurrentHashMap<>(); private Future<V> createFutureIfAbsent(final K key, final Callable<V> callable) {
Future<V> future = cache.get(key);
if (future == null) {
final FutureTask<V> futureTask = new FutureTask<V>(callable);
future = cache.putIfAbsent(key, futureTask);
if (future == null) {
future = futureTask;
futureTask.run();
}
}
return future;
} public V getValue(final K key, final Callable<V> callable) throws InterruptedException, ExecutionException {
try {
final Future<V> future = createFutureIfAbsent(key, callable);
return future.get();
} catch (final InterruptedException e) {
cache.remove(key);
throw e;
} catch (final ExecutionException e) {
cache.remove(key);
throw e;
} catch (final RuntimeException e) {
cache.remove(key);
throw e;
}
} public void setValueIfAbsent(final K key, final V value) {
createFutureIfAbsent(key, new Callable<V>() {
@Override
public V call() throws Exception {
return value;
}
});
} }

参考博客:

http://www.javacreed.com/how-to-cache-results-to-boost-performance/

编写线程安全的Java缓存读写机制 (原创)的更多相关文章

  1. Java缓存机制

    1 Java缓存 1.1 jvm内置缓存 Java中实现缓存的方式有很多,比如用static hashMap基于内存缓存的jvm内置缓存,简单不实用,保对象的有效性和周期无法控制,容易造成内存急剧上升 ...

  2. Java的多线程机制系列:(二)缓存一致性和CAS

    一.总线锁定和缓存一致性 这是两个操作系统层面的概念.随着多核时代的到来,并发操作已经成了很正常的现象,操作系统必须要有一些机制和原语,以保证某些基本操作的原子性.首先处理器需要保证读一个字节或写一个 ...

  3. 并发读写缓存实现机制(一):为什么ConcurrentHashMap可以这么快?

    大家都知道ConcurrentHashMap的并发读写速度很快,但为什么它会这么快?这主要归功于其内部数据结构和独特的hash运算以及分离锁的机制.做游戏性能很重要,为了提高数据的读写速度,方法之一就 ...

  4. Java缓存学习之二:浏览器缓存机制

    浏览器端的九种缓存机制介绍 浏览器缓存是浏览器端保存数据用于快速读取或避免重复资源请求的优化机制,有效的缓存使用可以避免重复的网络请求和浏览器快速地读取本地数据,整体上加速网页展示给用户.浏览器端缓存 ...

  5. 利用java反射机制编写solr通用的java客户端

    一.前言 通过上一篇的讲解,我们知道了dynamicFiled字段,它是动态的,不需要显示的声明.而且一些常用的基本类型solr已经默认给我们创建好了. 例如:*_i,*_is,等. 如果我们要使用动 ...

  6. java并发之线程同步(synchronized和锁机制)

    使用synchronized实现同步方法 使用非依赖属性实现同步 在同步块中使用条件(wait(),notify(),notifyAll()) 使用锁实现同步 使用读写锁实现同步数据访问 修改锁的公平 ...

  7. Map实现java缓存机制的简单实例

    缓存是Java中主要的内容,主要目的是缓解项目访问数据库的压力以及提升访问数据的效率,以下是通过Map实现java缓存的功能,并没有用cache相关框架. 一.缓存管理类 CacheMgr.java ...

  8. Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)

    一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...

  9. Java线程新特征——Java并发库

    一.线程池   Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利.为了编写高效稳定 ...

随机推荐

  1. 常用C字符串函数

    static void str_repalce(char *src,char *from,char *to) {     char *p,*q;     int lenFrom;     int le ...

  2. 2018.09.19 atcoder Snuke's Subway Trip(最短路)

    传送门 就是一个另类最短路啊. 利用颜色判断当前节点的最小花费的前驱边中有没有跟当前的边颜色相同的. 如果有这条边费用为0,否则费用为1. 这样跑出来就能ac了. 代码: #include<bi ...

  3. cmake-include_directories

    include_directories: Add include directories to the build. include_directories([AFTER|BEFORE] [SYSTE ...

  4. VHDL数据类型

    VHDL表示16进制 如 a : std_logic_vector(7 downto 0) 把0x55赋给a a <= x"55"; b表示二进制 b“1011_1111” ...

  5. 百度上传插件(webupload)单文件(单图片)上传设置

    var uploader = WebUploader.create({                 //auto : true,                 swf : '${ctx}/sta ...

  6. html5获取经纬度

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. C语言中union关键字

    union 关键字的用法与struct 的用法非常类似. union 维护足够的空间来置放多个数据成员中的“一种”,而不是为每一个数据成员配置空间,在union 中所有的数据成员共用一个空间,同一时间 ...

  8. ENVI数据格式

    选择一个或多个感兴趣的图层: 分类:目视解译.非监督分类 投影 哪里需要七参数,没有必要七参数吧?如果精度要求不高的话

  9. 解决:无法在发送 HTTP 标头之后进行重定向。 跟踪信息: 在 System.Web.HttpResponse.Redirect(String url, Boolean endResponse, Boolean permanent) 在 System.Web.Mvc.Async.AsyncControllerActionInvoker.<>……

    问题:在MVC的过滤器中验证用户状态时报如下错误:   无法在发送 HTTP 标头之后进行重定向. 跟踪信息:   在 System.Web.HttpResponse.Redirect(String  ...

  10. CG图形学的工具

    1. http://give.zju.edu.cn/cgcourse/new/frame/index.html 网页CG学习