首页IT科技java中double类型保留两位小数(Java 中的Double Check Lock(转))

java中double类型保留两位小数(Java 中的Double Check Lock(转))

时间2025-07-09 07:05:33分类IT科技浏览4653
导读:对于多线程编程来说,同步问题是我们需要考虑的最多的问题,同步的锁什么时候加,加在哪里都需要考虑,当然在不影响功能的情况下,同步越少越好,锁加的越迟越优是我们都必须认同的。DCL(Double Check Lock)就是为了达到这个目的。...

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

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

Java代码

public

class LazySingleton{

private

static

volatile LazySingletoninstance;

public

static LazySingletongetInstantce(){

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。

Java代码

public

class UserCacheDBService{

private

volatile Map<Long,UserDO>map= new ConcurrentHashMap<Long,UserDO>();

private Objectmutex= new Object(); /** *取用户数据,先从缓存中取                      ,缓存中没有再从DB取 *@paramuserId *@return */ public UserDOgetUserDO(LonguserId){ UserDOuserDO=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 UserDOgetUserFromDB(LonguserId){ //TODOAuto-generatedmethodstub return

null ;

} }
Java代码
publicclassUserCacheDBService{ privatevolatileMap<Long,UserDO>map=newConcurrentHashMap<Long,UserDO>(); privateObjectmutex=newObject(); /** *取用户数据                       ,先从缓存中取,缓存中没有再从DB取 *@paramuserId *@return */ publicUserDOgetUserDO(LonguserId){ UserDOuserDO=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); } returnuserDO; } privateUserDOgetUserFromDB(LonguserId){ //TODOAuto-generatedmethodstub returnnull; } }
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                      ,就没有进行④操作                      。

声明:本站所有文章               ,如无特殊说明或标注       ,均为本站原创发布        。任何个人或组织                      ,在未征得本站同意时               ,禁止复制               、盗用       、采集                      、发布本站内容到任何网站               、书籍等各类媒体平台       。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理                      。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
11的5211次方(5118的智能改写) vue实现markdown(Vue项目集成Markdown标记语言编辑器(MavonEditor))