qq_43304875 2023-05-29 14:43 采纳率: 40%
浏览 34

java,double check,这个double check有问题吗?

这个double check有问题吗?

引用:

1.http://blog.chinaunix.net/uid-28811518-id-5752680.html
2.https://www.cnblogs.com/yangchen-geek/p/15469076.html

1.问题

背景:某个方法需要动态生成对象,这些对象基于名称单例,为了较为效率的生成和获取,因此引入ConcurrentHashMap去先生成某个对象的锁,然后再获取该锁,再去初始化对象。但是生成的锁无法使用volatile修饰。代码类似如下,请问代码里面的double check有问题吗?

import org.apache.commons.lang.StringUtils;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class DynamicFeignClientBuilderV3 {
    /**
     * 本地缓存
     */
    private Map<String, Object> cache = new ConcurrentHashMap<>();

    /**
     * 锁映射
     */
    private Map<String, Object> lockMap = new ConcurrentHashMap<>();


    public <T, M> T getFeignClientUseModel(final Class<T> type, String targetServiceName, final Class<M> modelClass) {
        // 尝试从本地缓存获取
        T clientProxy = getFromLocalCache(targetServiceName, type);
        if (clientProxy != null) {
            return clientProxy;
        }

        String lockName = targetServiceName + "Lock";
        // 尝试生成一个锁(cas,并发下保证只有一个线程能put成功)
        lockMap.putIfAbsent(lockName, new Object());

        Object lockOfTargetServiceName = lockMap.get(lockName);
        clientProxy = getFromLocalCache(targetServiceName, type);
        if (clientProxy != null) {
            return clientProxy;
        }
        synchronized (lockOfTargetServiceName) {
            clientProxy = getFromLocalCache(targetServiceName, type);
            if (clientProxy != null) {
                return clientProxy;
            }

            Object target = new Object();
            cache.put(targetServiceName, target);
            return (T)target;
        }
    }

    private <T> T getFromLocalCache(String serviceName, Class<T> clazz) {
        if (StringUtils.isBlank(serviceName) || clazz == null) {
            throw new IllegalArgumentException("传入的参数中存在空值");
        }
        Object objectTemp = cache.get(serviceName);
        if (objectTemp == null) {
            return null;
        }
        if (clazz.isInstance(objectTemp)) {
            return (T) objectTemp;
        }
        throw new IllegalArgumentException(String.format("通过服务名称【%s】,获取到的实例类型为【%s】,而非【%s】", serviceName, objectTemp.getClass().getName(), clazz.getName()));
    }

}

2.笔者看法

笔者认为,double check是一种较为效率的解决单例重复初始化的方式,但是double check的引入带来了一些问题,即以下

XXX xxx  = new XXX()

可以拆分成
(1)申请内存区域
(2)内存区域初始化(赋值之类的)
(3)引用指向该内存区域
但是由于JMM(java memory model)会对指令重排序,所以导致了另外的线程第一次check的时候,由于引用已经指向了某内存区域,但是内存初始化还未完成,进而导致另外的线程使用内存区域里面的值时报错。而引入volatile可以避免指令重排序,即解决了上面的情况。


但是这种情况明显不能应用于笔者提供的代码,笔者是这么认为的。首先double check判断存不存在的依据是Map是否包含这个key,而由于我加了synchronized和使用了ConcurrentHashMap,能保证只能有一个线程put进去Map,而put的操作只是将某个引用指向某个内存区域,并没有一个初始化的过程,因此我判断我这个double check不存在问题

  • 写回答

1条回答 默认 最新

  • Ak混子464113225 2023-05-29 14:57
    关注

    在给定的代码中,存在一个双重检查锁定(double-checked locking)的机制。具体 体现在以下代码段:

    Object lockOfTargetServiceName = lockMap.get(lockName);
    clientProxy = getFromLocalCache(targetServiceName, type);
    if (clientProxy != null) {
        return clientProxy;
    }
    synchronized (lockOfTargetServiceName) {
        clientProxy = getFromLocalCache(targetServiceName, type);
        if (clientProxy != null) {
            return clientProxy;
        }
    
        Object target = new Object();
        cache.put(targetServiceName, target);
        return (T)target;
    }
    

    在双重检查锁定中,首先在没有锁的情况下检查对象是否存在,然后再在同步块内进行检查和创建。这种机制旨在减少同步块的开销,提高性能。

    然而,这段代码存在一个经典的线程安全问题,即指令重排序问题。在某些情况下,可能会导致其他线程获取到一个未完全初始化的对象。

    要解决这个问题,需要在 cachelockMap 变量声明时使用 volatile 关键字来确保可见性和禁止指令重排序。修改后的代码如下所示:

    private volatile Map<String, Object> cache = new ConcurrentHashMap<>();
    private volatile Map<String, Object> lockMap = new ConcurrentHashMap<>();
    

    使用 volatile 关键字修饰的变量在多线程环境下具有可见性,任何对该变量的修改都会立即被其他线程所感知,从而避免了指令重排序带来的问题。

    请注意,双重检查锁定在某些情况下可能不是必要的,具体取决于代码的上下文和要求。在某些情况下,使用简单的同步块即可满足线程安全的需求。因此,需要根据具体情况评估是否需要使用双重检查锁定。

    评论

报告相同问题?

问题事件

  • 创建了问题 5月29日

悬赏问题

  • ¥15 poi合并多个word成一个新word,原word中横版没了.
  • ¥15 【火车头采集器】搜狐娱乐这种列表页网址,怎么采集?
  • ¥15 求MCSCANX 帮助
  • ¥15 机器学习训练相关模型
  • ¥15 Todesk 远程写代码 anaconda jupyter python3
  • ¥15 我的R语言提示去除连锁不平衡时clump_data报错,图片以下所示,卡了好几天了,苦恼不知道如何解决,有人帮我看看怎么解决吗?
  • ¥15 在获取boss直聘的聊天的时候只能获取到前40条聊天数据
  • ¥20 关于URL获取的参数,无法执行二选一查询
  • ¥15 液位控制,当液位超过高限时常开触点59闭合,直到液位低于低限时,断开
  • ¥15 marlin编译错误,如何解决?