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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。