并发环境下Double-check模型的改进


  • 扩展阅读

    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的线程进来。



            最基本的处理方式如此:


Java代码 复制代码  收藏代码
  1. private static Map<String, Object> cache    
  2.                       = new ConcurrentHashMap<String, Object>();   
  3.            
  4.                 //Entry   
  5.         public Object run(String key) {   
  6.             Object result = cache.get(key);   
  7.             if (result == null) {   
  8.                 result = doHardWork(key);   
  9.                 cache.put(key, result);   
  10.             }   
  11.                
  12.             return result;   
  13.         }   
  14.            
  15.         private Object doHardWork(String key) {   
  16.             Object result = null;   
  17.             //Concrete work   
  18.             return result;    
  19.         }  
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; 
		}

        它的缺点很明显,同时会有多个相同key的线程在做事,资源浪费严重。

        先看段使用Double-check模式来完成相同功能的代码:

Java代码 复制代码  收藏代码
  1. private static Map<String, Object> cache    
  2.                             = new ConcurrentHashMap<String, Object>();   
  3.            
  4.         public Object run(String key) {   
  5.             Object result = cache.get(key);//First checking  
  6.             if (result == null) {   
  7.                 synchronized (cache) {   
  8.                     result = cache.get(key);//Second checking  
  9.                     if (result == null) {   
  10.                         result = doHardWork(key);   
  11.                         cache.put(key, result);   
  12.                     }   
  13.                 }   
  14.             }   
  15.                
  16.             return result;   
  17.         }   
  18.            
  19.         private Object doHardWork(String key) {   
  20.             Object result = null;   
  21.                
  22.             //Concrete work   
  23.                
  24.             return result;    
  25.         }  
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; 
		}

        假定某个线程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来满足需求。实现可以很简单,一是多个线程的数据共享,二是对于同样参数多个线程的通知。具体模型如下图:


        从代码来看:
Java代码 复制代码  收藏代码
  1.   /**  
  2.  * 用来标识当前参数有线程正在做具体逻辑  
  3.  */  
  4. public static Object lock = new Object();   
  5.   /**  
  6.  * 假定参数为'A',系统初始时检查lockMap中‘A’的value是否为null,如果为null,那当前线程就得做具体逻辑,把'A'的value设置为固定的lock,其它线程看到有这个lock就什么事也不做,然后suspend。当有返回数据时,将value由lock替换为正式返回数据,以在多个线程间共享 
  7.  */  
  8. rivate Map<String, Object> lockMap    
  9.                         = new ConcurrentHashMap<String, Object>();   
  10.   
  11.   /**  
  12.  * 所有suspend的线程都要在这里注册,以便随后得到通知  
  13.  */  
  14. 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>>();

	


        它的方法有:
Java代码 复制代码  收藏代码
  1. /*  
  2. *返回值是lock时,做具体逻辑,返回值不为lock时,是真正的返回数据,线程得到这个数据,直接返回 
  3. */  
  4. public Object runOrWait(String key);   
  5.   
  6. /*  
  7. *做具体逻辑的那个线程在做完事后,需要把result写入共享空间,让其它线程看到。然后通知所有注册这个参数的线程知道 
  8. */  
  9. 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。这个程序只是用来验证这个处理策略,对于细节问题,值得商榷,欢迎提出意见,十分感谢!
  • 大小: 17 KB
  • 大小: 18.7 KB
  • 大小: 20.4 KB
  • 大小: 51.4 KB
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值