终于研究明白了, concurrenthashmap 的 get 然后 put 的并发问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
gramyang
V2EX    Java

终于研究明白了, concurrenthashmap 的 get 然后 put 的并发问题

  •  
  •   gramyang 2019-06-18 10:48:44 +08:00 8637 次点击
    这是一个创建于 2386 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我参考的是这篇文章: https://www.cnkirito.moe/java-ConcurrentHashMap-CAS/ 网上论述这个的文章实在是不多。

    我的测试代码如下:

    public class test2 { private final static Map<String, Table> map new ConcurrentHashMap<>(); private static final String KEY = "key";

    public static void increase1(String key) { Table oldTable = map.get(key); int value = oldTable.getI(); oldTable.setI(value + 1); map.put(key, oldTable); } public static void increase2(String key) { Table oldTable; Table newTable = new Table(0); while(true) { oldTable = map.get(KEY); newTable.setI(oldTable.getI() + 1); if(map.replace(KEY, oldTable, newTable)) break; } } public static int getTableValue(String key) { return map.get(key).getI(); } public static void main(String[] args) { map.put(KEY, new Table(0)); ExecutorService executor = Executors.newFixedThreadPool(10); int callTime = 1000; CountDownLatch countDownLatch = new CountDownLatch(callTime); for(int i=0; i<callTime; i++) { executor.execute(new Runnable() { @Override public void run() { increase2(KEY); countDownLatch.countDown(); } }); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } executor.shutdown(); System.out.println("调用次数:" + getTableValue(KEY)); } 

    }

    class Table { private int i;

    public Table(int i) { this.i = i; } public int getI() { return i; } public void setI(int i) { this.i = i; } 

    }

    测试结果:使用 increase1 的话,调用次数是不停变动的,存在并发错误。而用 increase2 的话,恒定都是 1000.

    15 条回复    2019-09-02 23:30:28 +08:00
    annoymous
        1
    annoymous  
       2019-06-18 10:57:52 +08:00
    我觉着吧 CAS 的文章到处都是 一搜一大把
    WishingFu
        2
    WishingFu  
       2019-06-18 11:07:05 +08:00
    我觉得你这个例子跟 Map 关系不大,例 1 是 table 的 get 和 set 同步问题,map 的 put 没有意义,坐等大佬深入科普学习一波
    v2lf
        3
    v2lf  
       2019-06-18 12:26:02 +08:00
    CHM 只是保证 table 这个引用对所有的线程可见性(保证对象的正确发布),然而 Table 是不是线程安全的,不是 CHM 能控制的。CHM 每次的 put 也只能确保调用 put 方法的线程,刷新 local 内存到主内存(相当于一个类,只同步了 set 方法,没有同步 get 方法,所以这个类不是线程安全的)。increase2 运行正确 是因为你重新实例化了一个对象,相当于 Table 是事实不可变对象。
    v2lf
        4
    v2lf  
       2019-06-18 12:29:00 +08:00
    另外 我个人建议, 网上很多文章都是借鉴来借鉴去根据我看的经验来说,无法保证正确性,有些文章的描述,是老的 JMM,但是新的 JMM 已经增强了一些同步 y 元语,所以我的建议是看书 以及看 JUC 源码。
    [!]( http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#jsr133)
    书看 java 并发实战把 大神的那本书
    tslling
        5
    tslling  
       2019-06-18 12:58:11 +08:00 via Android
    你变动的是 CHM 里 value 的内部状态怎么能怪 CHM 呢? CHM 只保证这个 value 的同步呀,这里 table 不是线程安全的 CHM 也没办法
    alamaya
        6
    alamaya  
       2019-06-18 13:17:31 +08:00
    我觉得这篇文章写得没问题,你的理解可能有点问题
    chendy
        7
    chendy  
       2019-06-18 13:40:15 +08:00
    这里 map 的逻辑完全没用,就是证明了一下 Table 线程不安全…
    gramyang
        8
    gramyang  
    OP
       2019-06-18 14:00:50 +08:00
    @chendy
    @tslling
    @v2lf
    @WishingFu
    不要一看到 map 的 put 行为就两眼放光大呼你肯定是个小白,我当然知道非基础类型的引用是指针,不需要 put 回去。一点笔误而已。

    为什么要用 table ?首先你实际代码中用 concurrenthashmap,value 基本上不可能是基本类型或者包装类,都是复合类,用个 table 包装一下再正常不过了。所以网上那种用 AtomicInteger 来实现写安全的基本上没有什么实际意义。

    我这个帖子主要验证的是在 while 循环中 replace 成功后 break 的写法,针对的是 put 后 get 的问题,这应该是每个接触 concurrenthashmap 的人都踩过的坑吧??我不觉得这个发现烂大街,一点意义都没有。

    replace 方法的问题在于两个参数必须是 value 类型,实际使用中的 value 都是复合类型,不可能是基本类型或者是包装类,所以我加个 table 来验证一下。然而这个时候又涉及到了深拷贝,不过我这里没有体现。
    v2lf
        9
    v2lf  
       2019-06-18 14:15:05 +08:00
    @gramyang 大佬 我可没有直呼你是小白,我只是说了我的认为 算了, 不讨论了,安心看书
    wysnylc
        10
    wysnylc  
       2019-06-18 14:15:58 +08:00
    分布式环境下,公共变量不用 redis 的都是坑嗷
    本地(单机)并行处理当我没说
    firefffffffffly
        11
    firefffffffffly  
       2019-06-18 14:21:42 +08:00   1
    @gramyang

    每个接触 concurrenthashmap 的人都踩过的坑 ×
    不理解线程安全的人踩过的坑 √

    increase1 的线程不安全发生在 int value = oldTable.getI(); oldTable.setI(value + 1);这两句上
    increase2 使用 CAS 乐观锁的方式解决了 increase1 里线程不安全的问题,你也可以用传统的 synchornized 悲观锁同样解决问题。
    无论怎样都没有涉及到 CHM 的任何问题。
    micean
        12
    micean  
       2019-06-18 14:23:33 +08:00
    多线程操作一个 object,思路不应该是保证这个操作过程是线程安全的么,跟 map 没什么关系啊
    像这样
    class Table{
    void updateSafe();
    }

    map.get(KEY).updateSafe();
    gramyang
        13
    gramyang  
    OP
       2019-06-18 14:38:22 +08:00
    @firefffffffffly 是的,我确实对线程安全的理解不够深入
    zazalu
        14
    zazalu  
       2019-06-23 13:16:56 +08:00
    希望可以帮忙看下我这个问题,t/576609#reply0

    从楼主的问题引发出来的后续问题
    tslling
        15
    tslling  
       2019-09-02 23:30:28 +08:00
    @gramyang 我没有两眼放光。我没有大呼你肯定是个小白。不知道你有没有看到,我觉得 12 楼的思路更好,去确保 value 类型的线程安全性。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5676 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 34ms UTC 02:38 PVG 10:38 LAX 18:38 JFK 21:38
    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