ConcurrentHashMap的putIfAbsent可能存在的问题

本文介绍了一种在并发环境下安全创建单例对象的方法,并通过使用ConcurrentHashMap的putIfAbsent方法和自旋等待机制,避免了多次创建同一对象的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

转载自:http://blog.youkuaiyun.com/iter_zc/article/details/40543365

  在业务上可能会遇到有这种场景,全局维护一个并发的ConcurrentMap, Map的每个Key对应一个对象,这个对象需要只创建一次。如果Map中该key对应的value不存在则创建,否则直接返回。
  通常会使用ConcurrentHashMap的putIfAbsent(key, value) 方法来保证并发Map里面的这个key只有唯一的值。但是这种做法会造成某些线程无谓地创建了对象,尽管最后放入Map的是唯一的值。

 

public Registry getRegistry(String registryAddress){  
    Registry registry = registries.get(registryAddress);  
    if(registry == null){  
        // 第一次并发时可能有多个线程进入到这块代码,这些线程都执行创建对象  
        registry = createRegistry();  
        // 利用并发Map的putIfAbsent保证只有第一次创建的对象被放入Map  
        registries.putIfAbsent(registryAddress, registry);  
        // 再次获取一下,这时候是肯定不为空  
        registry = registries.get(registryAddress);  
    }  
    return registry;  
}

    这里registry可能会被多个线程创建出来,而且当createRegistry()是个很重的方法时,这种方法显然不是最优的。于是有了以下的改进:

// 记录自旋状态的轻量级类,只封装了一个volatile状态  
public static class SpinStatus{  
    volatile boolean released;  
}  
      
// 辅助并发控制的Map,用来找出每个key对应的第一个成功进入的线程  
private ConcurrentMap<String, SpinStatus> raceUtil = new ConcurrentHashMap<String, SpinStatus>();  
  
// 无锁实现单实例Registry的创建  
public Registry getRegistry(String registryAddress){  
    Registry registry = registries.get(registryAddress);  
    // 第一次创建  
    if(registry == null){  
        // 需要为并发的线程new一个自旋状态,只有第一个成功执行putIfAbsent方法的线程设置的SpinStatus会被共享  
        SpinStatus spinStatus = new SpinStatus();  
        SpinStatus oldSpinStatus = raceUtil.putIfAbsent(registryAddress, spinStatus);  
        //只有第一个执行成功的线程拿到的oldSpinStatus是null,其他线程拿到的oldSpinStatus是第一个线程设置的,可以在所有线程中共享  
        if(oldSpinStatus == null){  
            // 创建对象  
            registry = createRegistry();  
            // 放入共享的并发Map,后续线程执行get()方法后可以直接拿到非null的引用返回  
            registries.put(registryAddress, registry);  
            // 释放其他自旋的线程,注意,对第一个成功执行的线程使用的是spinStatus的引用  
            spinStatus.released = true;  
        }else{  
            // 其他线程在oldSpinStatus引用所指向的共享自旋状态上自旋,等待被释放  
            while(!oldSpinStatus.released){};  
        }  
              
        // 再次获取一下,这时候是肯定不为空  
        registry = registries.get(registryAddress);  
    }  
    return registry;  
}

上面的问题,ConcurrentHashMap#computeIfAbsent可以解决。

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值