ConcurrentHashMap的使用还有必要加锁吗?

背景

最近隔壁业务组新上了一个全新的系统,主要是提供了一批dubbo接口给我们组调用,预计qps大概在40k左右,由于是一个比较轻的微服务,压测阶段的P98大概在7ms左右,我们dubbo的超时时间设置的100ms,已经给足了阈值。但是在灰度验证阶段,发现大量的请求出现超时的情况,和压测结果完全不符,一顿排查和交流下来发现他们居然没有做预热。

最后他们又重新上了一版带预热的代码,刚上线时的超时问题随之解决。事后我大概cr了一下他们的预热代码,大概方案就是通过配置中心配置需要预热的dubbo接口和对应请求参数和一些其他的预热参数,他们通过线程池起了几个线程(线程数=预热接口数),各自的线程通过从配置中心获取的配置开始调用服务。在机器刚起来时发布系统会自动调用预热接口(如果存在),当预热效果达到设定结果时则停止预热,系统开始正式上线接受线上请求。

ConcurrentHashMap的使用还有必要加锁吗?

cr代码时发现了他们的以下代码:

   private static final Map<String, GenericService> SERVICE_MAP = Maps.newConcurrentMap();
   public static GenericService getGenericService(final DubboCaseInfo info) {
        final String key = info.getService() + "_" + info.getMethod() + "_" + info.getTypes().length;
        GenericService genericService = SERVICE_MAP.get(key);
        if (genericService == null) {
            synchronized (ReferenceRegister.class) {
                genericService = SERVICE_MAP.get(key);

                if (genericService == null) {
                    genericService = createGenericService(info);
                    SERVICE_MAP.put(key, genericService);
                }
            }
        }

        return genericService;
    }

上面这段代码主要是根据配置中心得到的配置初始化一个 GenericService 对象,后续再通过反射调用对应的dubbo服务。

GenericService 是 Apache Dubbo 中提供的一个接口,用于进行泛化调用(Generic Invocation)。泛化调用是一种不依赖具体接口类的调用方式,主要用于服务测试、网关开发等场景。通过 GenericService,客户端可以动态地调用服务提供者的任意方法,而无需在编译期依赖具体的服务接口。

可以看到使用了ConcurrentMap用来存储对应的key及dubbo服务,但是这里不仅使用了ConcurrentMap,还使用了一个双重校验锁来保证多线程下不会创建多个genericService 示例。看到这里其实心里是有一个疑问的:ConcurrentHashMap的使用还有必要加锁吗?因为ConcurrentHashMap 可以保证线程安全,因为它内部通过分段锁与CAS已经实现了高效的并发控制。

随后看了一些资料与博文,感觉结论还是仁者见仁智者见智,个人还是偏向于不用再使用双重校验锁,原因在于这只是一个简单的预热场景,使用双重检查锁定会增加代码复杂度。在某些情况下,简单的 putIfAbsent 操作就可以满足需求,同时保持代码简洁。

    public static GenericService getGenericService(final DubboCaseInfo info) {
        final String key = info.getService() + "_" + info.getMethod() + "_" + info.getTypes().length;
        return SERVICE_MAP.computeIfAbsent(key, k -> createGenericService(info));
    }

通过阅读源码也可以发现ConcurrentHashMap的putIfAbsent 内部实现使用了synchronized锁和CAS自旋来保证线程安全,相比只想爱HashMap的putIfAbsent方法则是不能保证线程安全的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值