• Java 中的Double Check Lock(转)


    Java 中的Double Check Lock(转) - - ITeye技术网站

    对于多线程编程来说,同步问题是我们需要考虑的最多的问题,同步的锁什么时候加,加在哪里都需要考虑,当然在不影响功能的情况下,同步越少越好,锁加的越迟越优是我们都必须认同的。DCL(Double Check Lock)就是为了达到这个目的。



    DCL简单来说就是check-lock-check-act,先检查再锁,锁之后再检查一次,最后才执行操作。这样做的目的是尽可能的推迟锁的时间。网上普遍举的一个例子是延迟加载的例子。

    Java代码

    1. public
       
      class
       LazySingleton {  
    2.     private
       
      static
       
      volatile
       LazySingleton instance;  
    3.       
    4.     public
       
      static
       LazySingleton getInstantce() {  
    5.         if
       (instance == 
      null
      ) {  
    6.             synchronized
       (LazySingleton.
      class
      ) {  
    7.                 if
       (instance == 
      null
      ) {  
    8.                     instance = new
       LazySingleton();  
    9.                 }  
    10.             }  
    11.         }  
    12.         return
       instance;  
    13.     }  


    对上面的例子来说,我们当然也可以把锁加载方法上,那样的话每次获取实例都需
    要获取锁,但其实对这个instance来说,只有在第一次创建实例的时候才需要同步,所以为了减少同步,我们先check了一下,看看这个
    instance是否为空,如果为空,表示是第一使用这个instance,那就锁住它,new一个LazySingleton的实例,下次另一个线程来
    getInstance的时候,看到这个instance不为空,就表示已经创建过一个实例了,那就可以直接得到这个实例,避免再次锁。这是第一个
    check的作用。



    第二个check是解决锁竞争情况下的问题,假设现在两个线程来请求getInstance,A、B线程同时发现instance为空,因为我们
    在方法上没有加锁,然后A线程率先获得锁,进入同步代码块,new了一个instance,之后释放锁,接着B线程获得了这个锁,发现instance已
    经被创建了,就直接释放锁,退出同步代码块。所以这就是check-lock-then check。


    网上有很多文章讨论DCL的失效问题,我就不赘述了,Java5之后可以通过将字段声明为volatile来避免这个问题。


    我推荐一篇很好的文章《用happen-before规则重新审视DCL》,里面讲的非常好。



    上面这个是最简单的例子,网上随处可见,双重检查的使用可不只限于单例的初始化,下面我举个实际使用中的例子。


    缓存用户信息,我们用一个hashmap做用户信息的缓存,key是userId。

    Java代码

    1. public
       
      class
       UserCacheDBService {  
    2.       
    3.     private
       
      volatile
       Map<Long, UserDO> map = 
      new
       ConcurrentHashMap<Long, UserDO>();  
    4.     private
       Object mutex = 
      new
       Object();  
    5.   
    6.     /**
       
    7.      * 取用户数据,先从缓存中取,缓存中没有再从DB取
       
    8.      * @param userId
       
    9.      * @return
       
    10.      */
        
    11.     public
       UserDO getUserDO(Long userId) {  
    12.         UserDO userDO = map.get(userId);  
    13.           
    14.         if
      (userDO == 
      null
      ) {                            ① check  
    15.             synchronized
      (mutex) {                       ② lock  
    16.                 if
       (!map.containsKey(userId)) {        ③ check  
    17.                     userDO = getUserFromDB(userId);    ④ act  
    18.                     map.put(userId, userDO);  
    19.                 }  
    20.             }  
    21.         }  
    22.           
    23.         if
      (userDO == 
      null
      ) {                             ⑤  
    24.             userDO = map.get(userId);  
    25.         }  
    26.           
    27.         return
       userDO;  
    28.     }  
    29.   
    30.     private
       UserDO getUserFromDB(Long userId) {  
    31.         // TODO Auto-generated method stub
        
    32.         return
       
      null
      ;  
    33.     }  
    34.   
    35. }  
    Java代码  收藏代码
    1. public class UserCacheDBService {  
    2.       
    3.     private volatile Map<Long, UserDO> map = new ConcurrentHashMap<Long, UserDO>();  
    4.     private Object mutex = new Object();  
    5.   
    6.     /** 
    7.      * 取用户数据,先从缓存中取,缓存中没有再从DB取 
    8.      * @param userId 
    9.      * @return 
    10.      */  
    11.     public UserDO getUserDO(Long userId) {  
    12.         UserDO userDO = map.get(userId);  
    13.           
    14.         if(userDO == null) {                            ① check  
    15.             synchronized(mutex) {                       ② lock  
    16.                 if (!map.containsKey(userId)) {        ③ check  
    17.                     userDO = getUserFromDB(userId);    ④ act  
    18.                     map.put(userId, userDO);  
    19.                 }  
    20.             }  
    21.         }  
    22.           
    23.         if(userDO == null) {                             ⑤  
    24.             userDO = map.get(userId);  
    25.         }  
    26.           
    27.         return userDO;  
    28.     }  
    29.   
    30.     private UserDO getUserFromDB(Long userId) {  
    31.         // TODO Auto-generated method stub  
    32.         return null;  
    33.     }  
    34.   
    35. }  





    三种做法:


    1、
    没有锁,即没有②和③,当在代码①处判断userDO为空之后,直接从DB取数据,这种情况下有可能会造成数据的错误。举个例子,A和B两个线程,A线程
    需要取用户信息,B线程更新这个user,同时把更新后的数据放入map。在没有任何锁的情况下,A线程在时间上先于B线程,A首先从DB取出这个
    user,随后线程调度,B线程更新了user,并把新的user放入map,最后A再把自己之前得到的老的user放入map,就覆盖了B的操作。B以
    为自己已经更新了缓存,其实并没有。



    2、
    没有第二次check,即没有③的情况,在加锁之后立即从DB取数据,这种情况可能会多几次DB操作。同样A和B两个线程,都需要取用户信息,A和B在进
    入代码①处时都发现map中没有自己需要的user,随后A线程率先获得锁,把新user放入map,释放锁,紧接着B线程获得锁,又从DB取了一次数据
    放入map。



    3、
    双重检查,取用户数据的时候,我们首先从map中根据userId获取UserDO,然后check是否取到了user(即user是否为空),如果没有
    取到,那就开始lock,然后再check一次map中是否有这个user信息(避免其他线程先获得锁,已经往map中放了这个user),没有的话,从
    DB中得到user,放入map。



    4、 在⑤处又判断了一次userDO为空的话就从map中取一次,这是由于此线程有可能在代码③处发现map中已经存在这个userDO,就没有进行④操作。

  • 相关阅读:
    WCF 绑定(Binding)
    WCF 配置服务 (02)
    WCF 双工模式
    .NET开源高性能Socket通信中间件Helios介绍及演示
    关于WCF服务在高并发情况下报目标积极拒绝的异常处理
    HTTP状态管理机制之Cookie
    JavaScript 总结几个提高性能知识点
    windows下nginx安装、配置与使用
    Windows下Nginx的安装与配置
    大型架构.net平台篇(WEB层均衡负载nginx)
  • 原文地址:https://www.cnblogs.com/lexus/p/2608849.html
Copyright © 2020-2023  润新知