`
annegu
  • 浏览: 99618 次
  • 性别: Icon_minigender_2
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

DCL,双重检查

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

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


public class LazySingleton {
	private static volatile LazySingleton instance;
	
	public static LazySingleton getInstantce() {
		if (instance == null) {
			synchronized (LazySingleton.class) {
				if (instance == null) {
					instance = new LazySingleton();
				}
			}
		}
		return instance;
	}
}


对上面的例子来说,我们当然也可以把锁加载方法上,那样的话每次获取实例都需要获取锁,但其实对这个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。


public class UserCacheDBService {
	
	private volatile Map<Long, UserDO> map = new ConcurrentHashMap<Long, UserDO>();
	private Object mutex = new Object();

	/**
	 * 取用户数据,先从缓存中取,缓存中没有再从DB取
	 * @param userId
	 * @return
	 */
	public UserDO getUserDO(Long userId) {
		UserDO userDO = map.get(userId);
		
		if(userDO == null) {                            ① check
			synchronized(mutex) {                       ② lock
				if (!map.containsKey(userId)) {        ③ check
					userDO = getUserFromDB(userId);    ④ act
					map.put(userId, userDO);
				}
			}
		}
		
		if(userDO == null) {                             ⑤
			userDO = map.get(userId);
		}
		
		return userDO;
	}

	private UserDO getUserFromDB(Long userId) {
		// TODO Auto-generated method stub
		return null;
	}

}


三种做法:
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,就没有进行④操作。

所以DCL只要记住:check-lock-check-act!
分享到:
评论
7 楼 coffeesweet 2010-10-15  
把第3步改成
userDO = map.get(userId);
if(null==userDO){

就不需要第5步了
6 楼 coffeesweet 2010-10-15  
请问,如果我这里的userDO是new出来的对象,还有必要加synchronized吗???
5 楼 liliugen 2010-09-05  
不过写得不错。很好!
4 楼 liliugen 2010-09-05  
这个 private Object mutex = new Object();  
应该是这样的吧 private static Object mutex = new Object();
小妹
3 楼 hedajia 2010-09-02  
很受用,向MM学习~~~
2 楼 tomleader 2010-08-31  
为啥不用reentrantLock
1 楼 zli.ray 2010-08-23  
每次看MM的文章,都感觉受益匪浅,真是巾帼不让须眉呀,惭愧惭愧

相关推荐

    双重检查锁

    双重检查锁(Double-Checked Locking, DCL)是一种在多线程环境中用于实现懒加载(lazy loading)的设计模式。它通过两次检查来确定是否需要获取锁,从而避免不必要的同步操作,提高程序性能。然而,DCL的实现并不像表面...

    Java双重检查加锁单例模式的详解

    DCL(Double-checked locking)是Java双重检查加锁单例模式的一种实现方法。它使用了synchronized关键字来确保线程安全,但是这也会带来性能损失。DCL看起来是一个聪明的优化,但是它却不能保证正常工作。 在多线程...

    DCL常用设计方法

    在这个主题下,“DCL常用设计方法”通常指的是如何在Java等编程语言中实现Double-Check Locking(双重检查锁定)和其他相关的设计模式来优化并发性能。以下将详细介绍DCL及其相关的知识点。 1. 双重检查锁定...

    单例模式(饿汉模式、懒汉模式、DCL单例模式、枚举)

    本文将详细讨论四种常见的单例实现方式:饿汉模式、懒汉模式、双重检查锁定(DCL)单例模式以及枚举单例。 1. **饿汉模式**: 饿汉模式是在类加载时就完成了实例化,避免了线程同步问题。这种方式简单且安全,但...

    单例模式七种写法_转

    文章提到的双重检查锁定(Double-Checked Locking,简称DCL)是一个在单例模式中用来优化性能的编程技巧。该技巧的核心在于减少同步的开销,在多线程环境下,仅在实例未被创建时才同步。然而,这个技巧在Java早期...

    单例模式的java实现

    }}在双重检查锁(DCL)实现中,我们首先在第一次检查时判断 instance 是否为 null,如果不是 null,则说明实例已经创建,直接返回。如果 instance 为 null,我们才进入同步代码块进行第二次检查,以防止多个线程...

    23钟设计模式之单例模式

    单例模式有多种实现方式,常见的包括懒汉式、饿汉式以及双重检查锁定(DCL)等。 1. 懒汉式:懒汉式是在第一次使用时才创建单例对象,以延迟加载提高效率。但是,如果在多线程环境下,没有正确处理同步问题,可能...

    JAVA中单例模式的几种实现方式.doc

    双重检查锁定(Double Checked Locking, DCL) 双重检查锁定是一种优化过的线程安全单例模式实现,通过减少不必要的同步操作提高效率。其实现思路是在外部先检查一次实例是否为null,如果不为null则直接返回实例,...

    单例模式-基本代码.rar_C++_Hikvision

    DCL通过双重检查避免了不必要的同步开销,同时确保了线程安全。 在Hikvision的门禁访问控制系统中,单例模式可能被用来管理设备连接、数据库连接或全局配置信息。例如,系统可能有一个`DeviceManager`类,负责与多...

    JAVA单例模式(三种)

    在本篇文章中,我们将详细探讨Java中的三种常见单例实现方式:饿汉式、懒汉式和双重检查锁定(DCL)式。 1. **饿汉式单例** 饿汉式单例在类加载时就完成了初始化,所以类加载比较慢,但获取单例对象的速度快且线程...

    单例模式,懒汉模式和饿汉模式

    在多线程环境下,线程安全的懒汉模式通常采用**双重检查锁定(Double-Checked Locking,DCL)**策略。这种策略是在获取单例实例时进行两次检查:第一次检查是在无须同步的情况下完成的,如果发现单例未被创建,则...

    单例模式案例-打印机案例.zip

    在C++中,单例模式的实现有多种方式,包括懒汉式、饿汉式、双重检查锁定(DCL)等。懒汉式是在第一次使用时才创建对象,而饿汉式则是在程序启动时就立即创建。DCL则是为了结合两者的优点,既延迟初始化,又保证线程...

    设计模式——单实例模式、单件模式——Java编写

    在Java中,实现单例模式主要有三种方式:饿汉式、懒汉式和双重检查锁定(DCL)。 1. 饿汉式: 饿汉式是在类加载时就完成了初始化,因此类加载比较慢,但获取实例的速度快,且线程安全。代码如下: ```java public...

    Java-single-model.rar_single

    在Java中实现单例模式,有多种方法,包括懒汉式、饿汉式、双重检查锁定(DCL)以及枚举方式。下面将详细介绍这些方法: 1. **懒汉式**:也称为延迟初始化,只有在第一次需要时才创建实例。这种方式看似简洁,但在多...

    线程安全的单例模式

    双重检查锁定(Double-Checked Locking, DCL)技术在懒汉式的同步基础上做了优化,只在第一次实例化时进行同步操作,避免了每次调用都同步带来的性能损耗。需要注意的是,这里使用了 `volatile` 关键字来确保可见性...

    设计模式+单例+应用场景+理解

    在Java中实现单例模式有多种方法,包括懒汉式、饿汉式、双重检查锁定(DCL)等。懒汉式是在第一次使用时才创建单例,而饿汉式是在类加载时就创建单例,两者都保证了线程安全,但饿汉式更高效。DCL是一种兼顾延迟初始...

    设计模式——单例模式(懒汉模式)

    在Java中,懒汉式的单例模式通常通过双重检查锁定(Double-Check Locking,DCL)来实现,这是一种线程安全的方式。下面是一个简单的DCL实现的懒汉式单例模式代码示例: ```java public class Singleton { private ...

    Java-single-instance.rar_java single_single_single-pass

    在Java中实现单例模式有多种方法,包括懒汉式、饿汉式、双重检查锁定(DCL)以及静态内部类方式。 1. 懒汉式:这是最直观的一种实现方式,即只有在第一次请求时才创建单例。但是,这种方式在多线程环境下不安全,...

    Java模式设计之单例模式[借鉴].pdf

    **双重检查锁定(DCL,Double-Checked Locking)**: 为了解决懒汉式的性能问题,有人提出了双重检查锁定,试图在保证线程安全的同时减少同步开销。然而在Java中,早期的DCL实现并不完全正确,因为Java内存模型的...

    006Singleton.rar

    3. **双重检查锁定(DCL)**:这是一种线程安全的懒汉式实现,它在实例化单例时添加了两次检查,第一次检查避免不必要的同步,第二次检查确保在多线程环境下只有一个实例被创建。这种方式结合了延迟初始化和线程安全...

Global site tag (gtag.js) - Google Analytics