请教一个 ConcurrentHashMap 问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
agzou
V2EX    Java

请教一个 ConcurrentHashMap 问题

  •  
  •   agzou 2022-05-20 16:26:11 +08:00 2652 次点击
    这是一个创建于 1308 天前的主题,其中的信息可能已经有所发展或是发生改变。
    public class IdGeneratorService { private final Map<String, AtomicLong> map = new ConcurrentHashMap<>(); public long nextId(String key) { // 虽然采用了并发安全的器,但是当 contains 语句通过后,有可能出现多线程先后 put,AtomicLong 值有可能给覆盖? if (!map.containsKey(key)) { AtomicLong atomicLOng= new AtomicLong(0); map.put(key, atomicLong); return atomicLong.incrementAndGet(); } return map.get(key).incrementAndGet(); } } 

    代码如上,如果并发调用 nextId(),我感觉即使使用了并发安全的容器,实际上这段代码也不是线程安全的,如果多线程访问,还是会出现 nextId()重复的问题,有可能 nextId 会出现多个 1 ?但是实际经过测试,并不会重现这个问题。。请教一下,这段代码是不是线程安全的,是否会生成重复 id?

    测试代码

     public static void main(String[] args) throws InterruptedException { int count=2000; CountDownLatch cdl=new CountDownLatch(count); IdGeneratorService service = new IdGeneratorService(); Map<Long, AtomicLong> countMap=new ConcurrentHashMap<>(); for(long i=1;i<=count;i++){ countMap.put(i,new AtomicLong()); } for(int i=0;i<count;i++){ new Thread(()->{ long id = service.nextId("test"); countMap.get(id).incrementAndGet(); cdl.countDown(); }).start(); } cdl.await(); boolean match = countMap.values().stream().mapToLong(AtomicLong::get).anyMatch(l->l>1); System.out.printf("id 重复=%b\n",match); } 
    第 1 条附言    2022-05-20 17:02:33 +08:00

    感谢各位!

    其实我的疑惑点是,我理解这段代码是会有线程问题的,但是我写的测试方法却没有测出来。

    我后面重复运行了10来次测试方法,能测出id重复的情况。

    结帖。

    15 条回复    2022-05-21 09:12:55 +08:00
    JeromeCui
        1
    JeromeCui  
       2022-05-20 16:37:45 +08:00
    public class IdGeneratorService {
    private final Map<String, AtomicLong> map = new ConcurrentHashMap<>();

    public long nextId(String key) {
    if (!map.containsKey(key)) {
    synchronized{
    if (!map.containsKey(key)) {
    AtomicLong atomicLOng= new AtomicLong(0);
    map.put(key, atomicLong);
    }

    }
    }
    return map.get(key).incrementAndGet();
    }
    }
    JeromeCui
        2
    JeromeCui  
       2022-05-20 16:38:06 +08:00
    ```
    public class IdGeneratorService {
    private final Map<String, AtomicLong> map = new ConcurrentHashMap<>();

    public long nextId(String key) {
    if (!map.containsKey(key)) {
    synchronized{
    if (!map.containsKey(key)) {
    AtomicLong atomicLOng= new AtomicLong(0);
    map.put(key, atomicLong);
    }

    }
    }
    return map.get(key).incrementAndGet();
    }
    }
    ```
    justNoBody
        3
    justNoBody  
       2022-05-20 16:39:25 +08:00
    我理解这个和`ConcurrentHashMap`没有关系,因为你用的`incrementAndGet`方法使用了 CAS ,即便是多个线程都同时拿到了这个`AtomicLong`的实例也没有关系
    Georgedoe
        4
    Georgedoe  
       2022-05-20 16:40:49 +08:00
    同一个 key 有可能会被 put 多次 , 某个 key 的 contains 和 put 不是原子操作 , 可以去看看 go 的 singleflight 的实现 , 保证一次只有一个线程执行了 set (put) 操作
    JeromeCui
        5
    JeromeCui  
       2022-05-20 16:42:02 +08:00
    完了,格式错乱了
    agzou
        6
    agzou  
    OP
       2022-05-20 16:46:12 +08:00
    @JeromeCui #2 我的问题是,我觉得我这段代码不是线程安全的,但测试却不会生成重复的 id
    wolfie
        7
    wolfie  
       2022-05-20 16:47:52 +08:00
    1. ID 不重复是因为 AtomicLog 。
    2. 初始化 test 小概率重复创建,直接用 computeIfAbsent 。
    agzou
        8
    agzou  
    OP
       2022-05-20 16:49:22 +08:00
    @justNoBody #3 但是这两句
    if (!map.containsKey(key)) {
    AtomicLong atomicLOng= new AtomicLong(0);
    map.put(key, atomicLong);
    return atomicLong.incrementAndGet();
    }

    有可能返回不同的两个 AtomicLong,这样调用 atomicLong.incrementAndGet(),应该会重复返回 1 ,但是我运行我的测试代码并没有重复 id
    Georgedoe
        9
    Georgedoe  
       2022-05-20 16:50:23 +08:00
    在你代码里加了点 log , 这是输出 , 很显然有问题

    public long nextId(String key) {
    // 虽然采用了并发安全的容器,但是当 contains 语句通过后,有可能出现多线程先后 put,AtomicLong 值有可能给覆盖?
    if (!map.containsKey(key)) {
    AtomicLong atomicLOng= new AtomicLong(0);
    System.out.println("put twice");
    map.put(key, atomicLong);
    long l = atomicLong.incrementAndGet();
    System.out.println(l);
    return l;
    }
    return map.get(key).incrementAndGet();
    }


    put twice
    put twice
    put twice
    1
    1
    2
    Kotiger
        10
    Kotiger  
       2022-05-20 16:52:20 +08:00
    正如四楼大佬所说,contains 和 put 组合在一起就不是安全操作了
    public class IdGeneratorService {
    private final Map<String, AtomicLong> map = new ConcurrentHashMap<>();

    public long nextId(String key) {
    // 直接用这个方法
    map.computeIfAbsent(key, it->new AtomicLong(0));
    return map.get(key).incrementAndGet();
    }
    }
    BBCCBB
        11
    BBCCBB  
       2022-05-20 16:56:28 +08:00
    用 computeIfAbsent ,

    有更复杂的场景, 就用 compute 方法, 不过这个方法更加的复杂
    BBCCBB
        12
    BBCCBB  
       2022-05-20 16:57:20 +08:00
    你这完美避开了 concurrentHashMap 的特性.
    justNoBody
        13
    justNoBody  
       2022-05-20 17:20:45 +08:00
    @agzou 你的测试代码和你的`nextId()`方法逻辑是不同的,我不是很理解你具体想要问啥。
    documentzhangx66
        14
    documentzhangx66  
       2022-05-21 07:47:08 +08:00
    资源的并行安全,本质是操作该资源的业务逻辑,在并行中要保证唯一与串行。

    当业务逻辑的唯一与串行,能够用 cas api 时,才会出现一行 cas api 语句就够了,比如经典的对同一个资源的 read & set 、compare & set 等等。

    但很多业务逻辑,可能需要同时操作不同资源、或者有其他复杂的操作逻辑,此时就不能用 cas 了,而应该老老实实的串行化(锁定)代码段。
    ihuotui
        15
    ihuotui  
       2022-05-21 09:12:55 +08:00
    没有深刻理解原子操作含义,如果理解了就不会有疑问。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5021 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 05:58 PVG 13:58 LAX 21:58 JFK 00:58
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86