java中double类型保留两位小数(Java 中的Double Check Lock(转))
对于多线程编程来说 ,同步问题是我们需要考虑的最多的问题 ,同步的锁什么时候加,加在哪里都需要考虑 ,当然在不影响功能的情况下 ,同步越少越好 ,锁加的越迟越优是我们都必须认同的 。DCL(Double Check Lock)就是为了达到这个目的 。
DCL简单来说就是check-lock-check-act ,先检查再锁 ,锁之后再检查一次 ,最后才执行操作。这样做的目的是尽可能的推迟锁的时间 。网上普遍举的一个例子是延迟加载的例子 。
Java代码
class LazySingleton{
privatestatic
volatile LazySingletoninstance;
publicstatic 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代码
class UserCacheDBService{
privatevolatile 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 returnnull ;
} }三种做法:
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版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!