对于多线程编程来说,同步问题是我们需要考虑的最多的问题,同步的锁什么时候加,加在哪里都需要考虑,当然在不影响功能的情况下,同步越少越好,锁加的越迟越优是我们都必须认同的。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!
分享到:
相关推荐
双重检查锁----新旧内存模型的对比!!JDK1.5是新的内存模型
用VB的Form直接生成DCL对话框程序极其调用的LSP文件 能自动对DCL对话框中的参数进行读取和赋值,并输出 使用步骤: 一、在VB中绘制好表单Form 注意:排列的时候尽量一行的Top坐标相同,这样能保证在同一行 二、将...
一个学习DCL对话框编程的好教程,适合于初级学者。
AutoLISP_DCL.AutoLISP_DCL.AutoLISP_DCL.AutoLISP_DCL.AutoLISP_DCL.AutoLISP_DCL.AutoLISP_DCL.AutoLISP_DCL.AutoLISP_DCL.
AutoCAD二次开发之DCL对话框自学教程
dcl常用的一些设计方法 主要是大量的一些实例
针对DCL专业课程提供期末报告的模板级样例
`1111111111111111111111111111111111111111111
对于喜欢CAD二次开发的人来说,Lisp程序好写,DCL程序不好写,Lisp对CAD来说有Visual Lisp编辑器,而DCL似乎不好用,而且可视化程度不高。 此软件对二次开发的人来说,可以说是一大福音,它将DCL所有控件已经做到...
第3篇(第12~21章)为autolisp与dcl对话框整合应用,主要介绍了dcl的基础知识、dcl对象属性的定义速查、autolisp掌控dcl的关键技巧;还介绍了编辑框、按钮和文字、切换开关、单选按钮、列表框、下拉菜单、图像,图像...
DCL代码生成器 (DCG) ,主要是cad 的lisp窗口用,非常不错。。
Autolisp编程学习资料,铁道出版社,非常好的书籍。
dcl驱动程序PPT课件.pptx
AUTOLISP@DCL基础篇(全)铁道出版社(推荐)
Product Description and Identification (Part Number) ...DCL Series of multi-layer chip inductors for choke. 2) Product Identification (Part Number) DCL ※※※※ 〇 XXX □ ◎ ① ② ③ ④ ⑤ ⑥
第3篇(第12~21章)为autolisp与dcl对话框整合应用,主要介绍了dcl的基础知识、dcl对象属性的定义速查、autolisp掌控dcl的关键技巧;还介绍了编辑框、按钮和文字、切换开关、单选按钮、列表框、下拉菜单、图像,图像...
CAD程序设计魔法书,CAD二次开发,AutoLISP&dcl;基础,中国铁道出版社,
实验三(2) SQL语言的DCL 一、实验目的 SQL的数据控制通过DCL(数据控制语言) 实现。DCL通过对数据库用户的授权和收权命令来实现有关数据的存取控制,以保证数据库的安全性。 本次实验了解DCL语言的GRANT和REVOKE...
AUTOLISP&DCL基础篇是AUTO LISP很好的教程