并发环境下Double-check模型的改进
来源:互联网 发布:到哪里投诉淘宝网 编辑:程序博客网 时间:2024/06/02 19:41
扩展阅读
http://www.51myit.com/thread-45338-1-1.html
public class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
简单场景:
多线程环境,每个线程携带惟一的key去组装数据,相同的key会有相同的数据结果。为了提高响应速度,在线程访问的入口处设置缓存。线程根据key先从缓存中取数据,如果缓存中没有,线程就去做具体的逻辑处理。
模型如下图:假定每个线程的key如A, B等,同时有多个携带同一key的线程进来。
最基本的处理方式如此:
- private static Map<String, Object> cache
- = new ConcurrentHashMap<String, Object>();
- //Entry
- public Object run(String key) {
- Object result = cache.get(key);
- if (result == null) {
- result = doHardWork(key);
- cache.put(key, result);
- }
- return result;
- }
- private Object doHardWork(String key) {
- Object result = null;
- //Concrete work
- return result;
- }
private static Map<String, Object> cache = new ConcurrentHashMap<String, Object>(); //Entrypublic Object run(String key) {Object result = cache.get(key);if (result == null) {result = doHardWork(key);cache.put(key, result);}return result;}private Object doHardWork(String key) {Object result = null;//Concrete workreturn result; }
它的缺点很明显,同时会有多个相同key的线程在做事,资源浪费严重。
先看段使用Double-check模式来完成相同功能的代码:
- private static Map<String, Object> cache
- = new ConcurrentHashMap<String, Object>();
- public Object run(String key) {
- Object result = cache.get(key);//First checking
- if (result == null) {
- synchronized (cache) {
- result = cache.get(key);//Second checking
- if (result == null) {
- result = doHardWork(key);
- cache.put(key, result);
- }
- }
- }
- return result;
- }
- private Object doHardWork(String key) {
- Object result = null;
- //Concrete work
- return result;
- }
private static Map<String, Object> cache = new ConcurrentHashMap<String, Object>();public Object run(String key) {Object result = cache.get(key);//First checkingif (result == null) {synchronized (cache) {result = cache.get(key);//Second checkingif (result == null) {result = doHardWork(key);cache.put(key, result);}}}return result;}private Object doHardWork(String key) {Object result = null;//Concrete workreturn result; }
假定某个线程T1的参数是A,如果它能从Cache中取到之前A的执行结果,就立马返回。否则在同步块外等待,期望此时在同步块中有另外一个参数也是A的线程T2正在运行,然后将运行结果放入缓存中,在T2执行完成退出同步块后,T1可以从Cache读取T2的执行结果,退出请求。Double-check模型有两次对Cache内容的check,一次在同步块外,一次在同步块里面。它的执行流程如图:
系统初始时,假定有30个参数,每个参数有10个请求线程,那么同时会有300个线程从Cache中读数据,在没有读到任何数据时,只会有一个线程进入同步块,其它299个线程在外面等着。Double-check的好处在于,每个参数第一个进入同步块的线程才会去执行正式逻辑,其它拥有同样参数的线程只要从Cache中取数据即可,效率很高。如果参数A的某个线程之前执行过,其它参数A的线程在进入同步块后,能从Cache中取到数据,立马退出同步块。但同时它的缺点就是因为有同步块的存在,每个参数的第一个线程不能并行进入具体逻辑执行过程,得一个一个的来。如此30个参数,每个参数的第一个线程得依次串行进入具体逻辑。
对于这样的应用场景,最好的流程是:相同参数的线程只有一个进入具体逻辑,其它线程等待这个参数的执行结果,在得到结果后,直接返回;不同参数的线程在具体逻辑阶段可以并发执行。期望的执行流程如下图:
这篇帖子的目的是改进Double-check模型的这种缺点,但不是修改Double-check来满足需求。实现可以很简单,一是多个线程的数据共享,二是对于同样参数多个线程的通知。具体模型如下图:
从代码来看:
- /**
- * 用来标识当前参数有线程正在做具体逻辑
- */
- public static Object lock = new Object();
- /**
- * 假定参数为'A',系统初始时检查lockMap中‘A’的value是否为null,如果为null,那当前线程就得做具体逻辑,把'A'的value设置为固定的lock,其它线程看到有这个lock就什么事也不做,然后suspend。当有返回数据时,将value由lock替换为正式返回数据,以在多个线程间共享
- */
- rivate Map<String, Object> lockMap
- = new ConcurrentHashMap<String, Object>();
- /**
- * 所有suspend的线程都要在这里注册,以便随后得到通知
- */
- private Map<String, List<Thread>> caller = new ConcurrentHashMap<String, List<Thread>>();
/** * 用来标识当前参数有线程正在做具体逻辑 */public static Object lock = new Object(); /** * 假定参数为'A',系统初始时检查lockMap中‘A’的value是否为null,如果为null,那当前线程就得做具体逻辑,把'A'的value设置为固定的lock,其它线程看到有这个lock就什么事也不做,然后suspend。当有返回数据时,将value由lock替换为正式返回数据,以在多个线程间共享 */private Map<String, Object> lockMap = new ConcurrentHashMap<String, Object>(); /** * 所有suspend的线程都要在这里注册,以便随后得到通知 */private Map<String, List<Thread>> caller = new ConcurrentHashMap<String, List<Thread>>();
它的方法有:
- /*
- *返回值是lock时,做具体逻辑,返回值不为lock时,是真正的返回数据,线程得到这个数据,直接返回
- */
- public Object runOrWait(String key);
- /*
- *做具体逻辑的那个线程在做完事后,需要把result写入共享空间,让其它线程看到。然后通知所有注册这个参数的线程知道
- */
- public void releaseLock(String key, Object result)
/**返回值是lock时,做具体逻辑,返回值不为lock时,是真正的返回数据,线程得到这个数据,直接返回*/public Object runOrWait(String key);/**做具体逻辑的那个线程在做完事后,需要把result写入共享空间,让其它线程看到。然后通知所有注册这个参数的线程知道*/public void releaseLock(String key, Object result)
具体程序见附件,里面有一个测试类,用来模拟测试Case。然后列举了以上出现的几种cache Demo。这个程序只是用来验证这个处理策略,对于细节问题,值得商榷,欢迎提出意见,十分感谢!
- 并发环境下Double-check模型的改进
- 单例模式下的double check
- Double Check Locking -- Java并发
- 并发控制(3) 使用double check方式的单例,来确保并发下的线程安全的单例模式
- java 的double-check locking
- 单例的double-check
- 线程安全的单例模型的演变与Double-Check-Locking的安全性
- 线程安全的单例模型的演变与Double-Check-Locking的安全性
- double check
- double-check
- 没理解double check lock的问题
- Double Check形式的单例模式
- Unity模型导入的Check
- LINUX环境并发服务器的三种实现模型
- LINUX环境并发服务器的三种实现模型
- LINUX环境并发服务器的三种实现模型
- LINUX环境并发服务器的三种实现模型
- LINUX环境并发服务器的三种实现模型
- C++组件简要介绍
- .net获取系统当前时间
- Java 线程_3 线程的交互与守护线程
- linux 知识积累
- php singleton()
- 并发环境下Double-check模型的改进
- 3D游戏场景DEMO——添加水体效果
- JVM基础剖析
- Java程序练习-统计字符数
- 凹凸贴图(bump mapping)概念
- SQL 修改数据库表的所有者
- hdu 1023 Train Problem II (卡特兰数)
- STM32 IO 流水灯
- Asp.net页面之间传递参数的几种方法