编写线程安全的Java缓存读写机制 (原创)
一种习以为常的缓存写法:
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缓存读写机制 (原创)的更多相关文章
- Java缓存机制
1 Java缓存 1.1 jvm内置缓存 Java中实现缓存的方式有很多,比如用static hashMap基于内存缓存的jvm内置缓存,简单不实用,保对象的有效性和周期无法控制,容易造成内存急剧上升 ...
- Java的多线程机制系列:(二)缓存一致性和CAS
一.总线锁定和缓存一致性 这是两个操作系统层面的概念.随着多核时代的到来,并发操作已经成了很正常的现象,操作系统必须要有一些机制和原语,以保证某些基本操作的原子性.首先处理器需要保证读一个字节或写一个 ...
- 并发读写缓存实现机制(一):为什么ConcurrentHashMap可以这么快?
大家都知道ConcurrentHashMap的并发读写速度很快,但为什么它会这么快?这主要归功于其内部数据结构和独特的hash运算以及分离锁的机制.做游戏性能很重要,为了提高数据的读写速度,方法之一就 ...
- Java缓存学习之二:浏览器缓存机制
浏览器端的九种缓存机制介绍 浏览器缓存是浏览器端保存数据用于快速读取或避免重复资源请求的优化机制,有效的缓存使用可以避免重复的网络请求和浏览器快速地读取本地数据,整体上加速网页展示给用户.浏览器端缓存 ...
- 利用java反射机制编写solr通用的java客户端
一.前言 通过上一篇的讲解,我们知道了dynamicFiled字段,它是动态的,不需要显示的声明.而且一些常用的基本类型solr已经默认给我们创建好了. 例如:*_i,*_is,等. 如果我们要使用动 ...
- java并发之线程同步(synchronized和锁机制)
使用synchronized实现同步方法 使用非依赖属性实现同步 在同步块中使用条件(wait(),notify(),notifyAll()) 使用锁实现同步 使用读写锁实现同步数据访问 修改锁的公平 ...
- Map实现java缓存机制的简单实例
缓存是Java中主要的内容,主要目的是缓解项目访问数据库的压力以及提升访问数据的效率,以下是通过Map实现java缓存的功能,并没有用cache相关框架. 一.缓存管理类 CacheMgr.java ...
- Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)
一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...
- Java线程新特征——Java并发库
一.线程池 Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利.为了编写高效稳定 ...
随机推荐
- oracle 新建数据库 ,新建用户
net manager 数据库名----电脑名localhost 1521 , 服务名 orcl (oracle 版本不一样, 不同版本不一样,,) 然后测试.. sys 账号登录 新建用 ...
- Java中的内存划分
Java程序在运行时,需要在内存中分配空间.为了提高运行效率,就对数据进行了不同的空间划分.因为每一片区域都有特定的数据处理方式和内存管理方式. 具体分为5种内存空间: 程序计数器:保证线程切换后能恢 ...
- java实现从url路径中下载pdf文档到本地
package com.cellstrain.icell.util; import java.io.*;import java.net.*; public class DownloadPdf { /* ...
- 2018.08.19 NOIP模拟 change(简单模拟)
Change 题目背景 SOURCE:NOIP2015-SHY-10 题目描述 Alice 和 Bob 又聚在一起了!他们已经厌倦了取石子游戏,现在他们热衷于切题.于是,Alice 找到了一道题让 B ...
- 手机PC文件传输
QQ啥的现在直接无法全部退出,很纠结后台运行,时不时的来条消息,明明电脑QQ还开着,越来越流氓了. 服务端代码: <%@ Page Language="C#" %> & ...
- linux week3
2.如何快速的回到 上⼀一次所在的位置 cd An argument of - is equivalent to $OLDPWD. cd - #cd $OLDPWD cd - #快速的回到 上 ...
- UVa 12003 Array Transformer (分块)
题意:给定一个序列,然后有 m 个修改,问你最后的序列是什么,修改是这样的 l r v p 先算出从 l 到 r 这个区间内的 小于 v 的个数k,然后把第 p 个的值改成 k * u / (r - ...
- 反爬虫破解系列-汽车之家利用css样式替换文字破解方法
网站: 汽车之家:http://club.autohome.com.cn/ 以论坛为例 反爬虫措施: 在论坛发布的贴子正文中随机抽取某几个字使用span标签代替,标签内容位空,但css样式显示为所代替 ...
- node csv
想实现下载csv文件的功能,内容放在mysql的blob中,在网上找的都是关于csv模块的. 由于csv的更新,网上的很多方法都用不了,比如csv(),现在已经变了:http://csv.adalta ...
- ACL登陆认证
前篇文章ACL授权实例介绍了授权,授权完成之后,就要进行认证.ACL的认证主要分为登陆认证与即时认证.所谓登录认证就是在用户登陆的时候,进行信息认证.根据用户Id,加载上来该用户所拥有的权限模块:而即 ...