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

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/

最新文章

  1. FZU 2137 奇异字符串 后缀树组+RMQ
  2. 12306火车票查询--python
  3. UITextView 文本垂直居中
  4. Hive0.11安装配置学习笔记
  5. Oracle表名、列名、约束名的长度限制
  6. 【安卓特效】怎样给ImageView加上遮罩,点击时泛黑、或泛白、?
  7. 恢复WinMend Folder Hidden隐藏的文件
  8. 【MySQL】MySQL中针对大数据量常用技术_创建索引+缓存配置+分库分表+子查询优化(转载)
  9. ☀【Zepto】
  10. (转)ASP.NET版本的Kindeditor插件的使用(同步)
  11. 可变参数列表-Java SE5新特性(转)
  12. Python的字符串格式化符号
  13. Effective C++_笔记_条款04_确定对象被使用之前已先被初始化
  14. 基于python第三方requests 模块的HTTP请求类
  15. C# 垃圾回收机制(转)
  16. 关于使用微信js上传图片 笔记
  17. python 机器学习 决策树
  18. 一起学Linux04之Linux文件基本属性
  19. vue-11-路由嵌套-参数传递-路由高亮
  20. Django+Xadmin打造在线教育系统(三)

热门文章

  1. li.active2有加强重要性的效果。
  2. 美团点评2017校招笔试真题-算法工程师B
  3. 2018.09.16 bzoj3757: 苹果树(树上莫队)
  4. android apk签名原理
  5. cordova 选择图片并上传到服务器
  6. java-Runtime 调用命令
  7. cmake-add_definitions
  8. MySQL-5.7.10主主同步的安装和配置
  9. Hdu1978 How many ways 2017-01-18 14:32 40人阅读 评论(0) 收藏
  10. ESP32应用程序的内存布局