一头扎进多线程-构建高效且可伸缩的结果缓存
2016-12-09 16:07
387 查看
通过多线程的所有组件学习之后,我们要学习到如何把一个类包装成一个多线程安全类,下面通过构造一个——计算缓存类,在构造的过程中一步一步的优化,最终来得到我们想要的计算缓存类。
类提供的功能:类提供一个计算的功能,然后把计算传入的值与结果缓存在一个Map中,当第二次计算时先从缓存里面查看看曾经有没有计算过,有的话就直接返回结果,没有的话就进行计算,存到缓存再返回结果。
定义一个计算的接口,里面提供一个计算的方法,等待实现
定义一个具体的计算类,用来实现计算的功能
最后定义一个主类来实现我们想要的逻辑,这里使用的是组合的方式来进行计算
解析上面的代码,我们可以看到,由于hashMap是非线程安全的,所以我们只能通过synchronized 加一个隐形锁来对我们的逻辑进行锁定,但是这样导致我们发生并发时,我们会因为计算时间的关系导致线程间进行长时间的等待,降低了吞吐率。那我们接下来看看如何改进。
ConcurrentHashMap内部实现机构是分段锁,去除隐式锁换成并发Map有助于我们增加吞吐量,不过这里又存在另外的一个问题。
所以我们现在把原来的
我们直接把计算的步骤放到FutureTask里面,让他帮我们进行处理,我们直接把FutureTask放到缓存里面,需要的时候再get出来,这样就可以解决了我们第二个点提到的那个技术难题。改进后的Memosize如下:
虽然上面的代码看起来好像几乎是完美的,但是不要忘了还存在一个非原子操作
类提供的功能:类提供一个计算的功能,然后把计算传入的值与结果缓存在一个Map中,当第二次计算时先从缓存里面查看看曾经有没有计算过,有的话就直接返回结果,没有的话就进行计算,存到缓存再返回结果。
菜鸟的做法
如果是我的话,首先我会这么做定义一个计算的接口,里面提供一个计算的方法,等待实现
public interface Computable<A,V> {//V代表返回结果 V computer(A arg);//A表示参数 }
定义一个具体的计算类,用来实现计算的功能
public class ExpensiveFunction implements Computable<String,BigInteger> { /* (非 Javadoc) * @see com.jjt.cache.Computable#computer(java.lang.Object) */ @Override public BigInteger computer(String arg) { //模拟经过长时间的计算 return new BigInteger(arg); } }
最后定义一个主类来实现我们想要的逻辑,这里使用的是组合的方式来进行计算
public class Memosizer<A,V> implements Computable<A, V>{ private final Map<A,V> cache = new HashMap<A,V>();//缓存数据结构 private final Computable<A,V> c;//具体计算类 /** * */ public Memosizer(Computable<A, V> c) { this.c=c;//构造函数传入一个具体的计算类 } /* (非 Javadoc) * @see com.jjt.cache.Computable#computer(java.lang.Object) */ @Override public synchronized V computer(A arg) {//实现我们想要的逻辑 V result = cache.get(arg); if(result==null){ result = c.computer(arg); cache.put(arg, result); } return result; } }
解析上面的代码,我们可以看到,由于hashMap是非线程安全的,所以我们只能通过synchronized 加一个隐形锁来对我们的逻辑进行锁定,但是这样导致我们发生并发时,我们会因为计算时间的关系导致线程间进行长时间的等待,降低了吞吐率。那我们接下来看看如何改进。
把线程安全性交给线程安全Map类管理
// private final Map<A,V> cache = new HashMap<A,V>(); private final Map<A,V> cache = new ConcurrentHashMap<A,V>();
ConcurrentHashMap内部实现机构是分段锁,去除隐式锁换成并发Map有助于我们增加吞吐量,不过这里又存在另外的一个问题。
延时任务交给FutureTask
我们已经知道有一个类能基本实现这个功能:FutureTask。FutureTask表示一个计算的过程。这个过程可能是已经计算完成,也可能是正在进行。如果结果可用,那么 FutureTask.get将立即返回,否则会一直阻塞,直到结果计算出来再将其返回。所以我们现在把原来的
ConcurrenHashMap<A,V>,更改为ConcurrentHashMap<A,Future<V>>
我们直接把计算的步骤放到FutureTask里面,让他帮我们进行处理,我们直接把FutureTask放到缓存里面,需要的时候再get出来,这样就可以解决了我们第二个点提到的那个技术难题。改进后的Memosize如下:
public class Memosizer<A,V> implements Computable<A, V>{ // private final Map<A,V> cache = new HashMap<A,V>(); private final Map<A,Future<V>> cache = new ConcurrentHashMap<A,Future<V>>(); private final Computable<A,V> c;//具体计算类 /** * */ public Memosizer(Computable<A, V> c) { this.c=c; } /* (非 Javadoc) * @see com.jjt.cache.Computable#computer(java.lang.Object) */ @Override public V computer(A arg) throws InterruptedException, ExecutionException {//去除分段锁之后 Future<V> result = cache.get(arg); if(result==null){ Callable<V> callable = new Callable<V>() { @Override public V call() throws Exception { return c.computer(arg); } }; FutureTask<V> f = new FutureTask<V>(callable); result=f; cache.put(arg, result); f.run();//这里将调用c.compute方法,在这里线程被阻塞等没关系,因为futureTask已经进了缓存了 } return result.get(); } }
虽然上面的代码看起来好像几乎是完美的,但是不要忘了还存在一个非原子操作
高手最后的进阶
相关文章推荐
- java多线程(十) 之 构建高效且可伸缩的结果缓存
- 构建高效且可伸缩的结果缓存
- java并发工具类构建高效且可伸缩的结果缓存
- java并发——构建高效且可伸缩的结果缓存
- 构建高效且可伸缩的结果缓存
- 【JAVA并发编程实战】5、构建高效且可伸缩的结果缓存
- 构建高效可伸缩的结果缓存
- java并发编程实战-构建高效且可伸缩的结果缓存
- Java并发(具体实例)——构建高效且可伸缩的结果缓存
- 构建高效且可伸缩的结果缓存引申的并发测试规范化
- java并发编程实践学习(5)构建块为计算结果建立高效,可伸缩的高速缓存
- Java并发(具体实例)—— 构建高效且可伸缩的结果缓存
- 《Java并发编程实战》 阅读笔记 构建高效且可伸缩的结果缓存
- 【多线程_提高篇】 创建高效且可伸缩的结果缓存
- 高效可伸缩的结果缓存
- 构建高效的结果缓存
- 高效且可伸缩的结果缓存
- 高效可伸缩的结果缓存
- Java趣谈——如何构建一个高效且可伸缩的缓存
- Java并发编程之为计算结果建立高效、可伸缩的高速缓存