double checked locking,Java double check

  double checked locking,Java double check

  Java - ITeye技术网站中的双重检查锁

  Java中的双重检查锁

  博客类:多线程与并发编程Java多线程编程对于多线程编程来说,同步是我们需要考虑的最重要的问题。需要考虑何时何地添加同步锁。当然同步越少越好,加锁越晚越好,一定要一致。DCL(双重检查锁)就是为了这个目的。

  DCL只是一个检查-锁定-检查-动作。先检查后锁定,锁定后再检查,最后执行操作。这样做的目的是尽可能延迟锁定时间。网上经常引用的一个例子就是延迟加载的例子。

  Java代码

  公众的

  班级

  LazySingleton

  私人的

  静电

  不稳定的

  LazySingletoninstance

  公众的

  静电

  lazysingletingetinstance(){

  如果

  (实例==

  空

  ){

  同步的

  (LazySingleton。

  班级

  ){

  如果

  (实例==

  空

  ){

  实例=新的

  LazySingleton();

  } } }返回

  实例;

  } }

  对于上面的例子,当然,我们也可以在方法上加载锁,在这种情况下,我们每次都需要获取实例。

  来获得锁,但实际上,对于这个实例,只有在第一次创建实例时才需要同步,所以为了减少同步,让我们先检查一下这个。

  实例是否为空?如果是空的,说明是第一次使用这个实例,然后锁定它,新建一个LazySingleton的实例,下次再来一个线程。

  在获取实例时,当看到这个实例不为空时,说明已经创建了一个实例,可以直接获取这个实例,避免再次锁定。这是第一个。

  检查的功能。

  第二个检查是解决锁竞争的问题。假设现在有两个线程请求getinstance,线程A和B同时发现该实例为空,因为我们

  方法上没有锁,然后线程A率先获得锁,进入同步代码块,新创建一个实例,然后释放锁,然后线程B获得锁,发现实例有

  一旦它被创建,锁将被直接释放,同步代码块将退出。所以这是支票-锁-然后支票。

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

  推荐一篇好文章《用happen-before规则重新审视DCL》,很不错。

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

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

  Java代码

  公众的

  班级

  UserCacheDBService{

  私人的

  不稳定的

  Map Long,UserDO map=

  新的

  UserDO,ConcurrentHashMap Long

  私人的

  Objectmutex=

  新的

  object();

  /**

  *获取用户数据,首先从缓存中获取,而不是从缓存中的数据库获取。

  *@paramuserId

  * @返回

  */

  公众的

  UserDOgetUserDO(LonguserId){

  UserDOuserDO=map . get(userId);如果

  (userDO==

  空

  ){检查

  同步的

  (互斥){锁定

  如果

  (!map . contains key(userId)){检查

  userDO=getUserFromDB(userId);act map.put(userId,userDO);} } }如果

  (userDO==

  空

  ){

  userDO=map . get(userId);}返回

  userDO

  }私人

  UserDOgetUserFromDB(long userid){

  //TODOAuto-generatedmethodstub

  返回

  空

  ;

  }} Java代码publiclassusercachedservice { PrivateVolatileMapLong,UserDomap=NewConcurrenthashMapLong,UserDo PrivateObjectMutex=new object();/* * *先从缓存中获取用户数据,但缓存中不再有DB的数据* @ param userid * @ return */public userdogetuserdo(long userid){ userdouserdo=map . Get(userid);if(userDO==null){check synchronized(mutex){lock if(!map . contains key(userId)){check userdo=getUserFromDB(userId);actmap.put(userId,userDO);} } } if(user do==null){user do=map . get(userId);} returnuserDO} privateUserDOgetUserFromDB(long userid){//todo auto-generatedmethodstuberturnnull;} }公共类UserCacheDBService {

  私有可变映射Long,UserDO Map=new concurrent hashmap Long,UserDO

  私有对象互斥=new Object();

  *获取用户数据,首先从缓存中获取,而不是从缓存中的数据库获取。

  * @param userId

  * @返回

  public UserDO get UserDO(Long userId){

  UserDO UserDO=map . get(userId);

  if(userDO==null) { 检查

  同步(互斥){ 锁

  如果(!map.containsKey(userId)) { 检查

  userDO=getUserFromDB(userId);行动

  map.put(userId,userDO);

  if(userDO==null) {

  userDO=map . get(userId);

  返回userDO

  private UserDO getUserFromDB(Long userId){

  //TODO自动生成的方法存根

  返回null

  }

  三种方法:

  1、

  没有锁,也就是没有和。当在代码判断userDO为空时,直接从DB取数据,可能会造成数据错误。比如有两个线程A和B,线程A。

  为了获取用户信息,线程B更新这个用户,并将更新后的数据放入map。在没有任何锁的情况下,线程A在时间上领先于线程B,A首先从d B中取出这个。

  用户,然后线程调度,线程B更新用户,将新用户放入map。最后A把之前得到的老用户放到地图里,覆盖B的操作

  我已经更新了我的缓存,但是我没有。

  2、

  如果没有第二次检查,即没有,那么锁被锁定后,数据将立即从DB中取出。在这种情况下,可能会有更多的DB操作。同样,两个线程A和B需要获取用户信息,A和B正在进行中。

  当输入代码时,他们都发现地图中没有他们需要的用户。然后线程A率先获得锁,将新用户放入map,释放锁。然后线程B获得锁,并再次从d B获取数据。

  进入地图。

  3、

  双重检查,在获取用户数据时,我们首先根据userId从map中获取UserDO,然后检查是否获取了用户(即用户是否为空)。如果没有,

  获取它,然后启动锁,然后再次检查map中是否有这个用户信息(为了避免其他线程先获取锁,他们已经把这个用户放到map中了)。如果没有,从开始

  从数据库中获取用户,并将其放入地图。

  4.如果在判断userDO再次为空,它将从地图中提取一次。这是因为这个线程可能发现这个userDO已经存在于map中的代码处,所以它不执行操作。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: