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

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. 2018.10.22 cogs2471. [EZOI 2016]源氏的数学课(线段树)

    传送门 线段树入门操作. 直接把题目给的(r−i+1)∗a[i](r-i+1)*a[i](r−i+1)∗a[i]拆开变成(r+1)∗1∗a[i]−i∗a[i](r+1)*1*a[i]-i*a[i](r ...

  2. 2018.10.18 bzoj1185: [HNOI2007]最小矩形覆盖(旋转卡壳)

    传送门 不难看出最后的矩形一定有一条边与凸包某条边重合. 因此先求出凸包,然后旋转卡壳求出当前最小矩形面积更新答案. 代码: #include<bits/stdc++.h> #define ...

  3. 2018.06.30 cdq分治

    #cdq分治 ##一种奇妙的分治方法 优点:可以顶替复杂的高级数据结构:常数比较小. 缺点:必须离线操作. CDQ分治的基本思想十分简单.如下: 我们要解决一系列问题,包含修改和查询操作,我们将这些问 ...

  4. 2018.09.16 loj#10243. 移棋子游戏(博弈论)

    传送门 题目中已经给好了sg图,直接在上面跑出sg函数即可. 最后看给定点的sg值异或和是否等于0就判好了. 代码: #include<bits/stdc++.h> #define N 2 ...

  5. 2018.08.28 洛谷P4360 [CEOI2004]锯木厂选址(斜率优化dp)

    传送门 一道斜率优化dp入门题. 是这样的没错... 我们用dis[i]表示i到第三个锯木厂的距离,sum[i]表示前i棵树的总重量,w[i]为第i棵树的重量,于是发现如果令第一个锯木厂地址为i,第二 ...

  6. BZOJ 1935 Tree 园丁的烦恼 (树状数组)

    题意:中文题. 析:按x排序,然后用树状数组维护 y 即可. 代码如下: #pragma comment(linker, "/STACK:1024000000,1024000000" ...

  7. Java解决高并发方案(帮助你我他)

           一个小型的网站,可以使用最简单的html静态页面就实现了,配合一些图片达到美化效果,所有的页面均存放在一个目录下,这样的网站对系统架构.性能的要求都很简单.随着互联网业务的不断丰富,网站 ...

  8. (小数化分数)小数化分数2 -- HDU --1717

    链接: http://acm.hdu.edu.cn/showproblem.php?pid=1717 举例: 0.24333333…………=(243-24)/900=73/3000.9545454…… ...

  9. android build.gradle(groovy)

    一.build.Gradle 这个 build.Gradle 文件来自 drakeet 大神的 Meizi 项目我直接在代码上加注释,参照着注释看代码就行,是不是发现有很多代码平时都没看见过. //M ...

  10. 配置Jenkins构建失败触发邮件报警机制

    系统管理 1.进入系统管理-->系统设置 定位到Jenkins Location配置项   配置系统管理员邮件地址 系统管理员邮件地址需要同发送报警邮件地址相同 定位到邮件通知   配置SMTP ...