ConcurrentHashMap 多线程并发对值+1 总数不对

睡觉了喂 2018-09-14 04:19:36
模拟1000个线程 对ConcurrentHashMap里的一个value做+1

如果顺利应该是1001

将每次加一的结果打印出来放到sublime里 共有1000行说明线程是跑完了


循环外打印最终结果


但总是结果不对

尝试过将integer换成AtomicInteger 并用incrementAndGet做自增,结果只有一次正确


public static void main(String[] args) throws InterruptedException {

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);

ExecutorService executorService = Executors.newFixedThreadPool(100);
// CountDownLatch countDownLatch = new CountDownLatch(100);

for (int i = 0; i < 1000; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
int key = map.get("key") + 1;
map.put("key", key);
System.out.println(key);
}
});
}
Thread.sleep(3000); //模拟等待执行结束
System.out.println("------" + map.get("key") + "------");
executorService.shutdown();
}


...全文
1051 16 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
16 条回复
切换为时间正序
请发表友善的回复…
发表回复
哎呀熊熊熊 2020-04-01
  • 打赏
  • 举报
回复
也就是说,ConcurrentHashMap 并不能在并发环境,多线程时边存边取,不然会有不一致的问题,可以这么理解吗?
睡觉了喂 2018-09-20
  • 打赏
  • 举报
回复
结贴 , 代码如下
public static void main(String[] args) throws InterruptedException {

        ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<>();
        AtomicInteger value = new AtomicInteger(1);
        map.put("key", value);
        System.out.println("------" + map.get("key") + "------");

        ExecutorService executorService = Executors.newFixedThreadPool(1000);
        for (int i1 = 0; i1 < 100; i1++) {

            for (int i = 0; i < 1000; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        map.get("key").incrementAndGet();
                    }
                });
            }
            Thread.sleep(3000); //模拟等待执行结束
//        Thread.yield();
            System.out.println("------" + map.get("key") + "------");
        }

        executorService.shutdown();
    }
睡觉了喂 2018-09-20
  • 打赏
  • 举报
回复
引用 12 楼 weixin_43220556 的回复:
1000个线程,并不全是按顺序执行的 举个简单的例子 A1线程刚把key从map读出后,在回写入map前,A2线程抢得时间片运行了,此时A1与A2将有一个无效。 使用AtomicInteger后,通过incrementAndGet保证了累加的效果,但set并不能保证按顺序执行。你可以直接用map.get("key").incrementAndGet()不需要再set,如果仅是体现累加效果,此时的map显得有点多余,用value自身就能解决问题 incrementAndGet()也是通过同步锁来实现的,所以如果你需要实现更复杂的逻辑,还是需要自己加同步锁的 另外,可以通过输出线程序号i与对应的value来观察线程的执行情况
多谢解释 , 就是说incrementAndGet 和 set单独使用都是原子操作 , 结合后就不是原子操作 , 而AtomicInteger本身是对象可以直接操作不用赋值
睡觉了喂 2018-09-20
  • 打赏
  • 举报
回复
引用 13 楼 qq_35175535 的回复:
大哥,你是真的不懂原子级类啊,value.set(map.get("key").incrementAndGet());改成map.get("key").incrementAndGet();
我是真不懂 平时没有用正在学习,,, 改了以后好了,感谢
wildyy 2018-09-20
  • 打赏
  • 举报
回复
大哥,你是真的不懂原子级类啊,value.set(map.get("key").incrementAndGet());改成map.get("key").incrementAndGet();
鸣笛 2018-09-20
  • 打赏
  • 举报
回复
1000个线程,并不全是按顺序执行的 举个简单的例子 A1线程刚把key从map读出后,在回写入map前,A2线程抢得时间片运行了,此时A1与A2将有一个无效。 使用AtomicInteger后,通过incrementAndGet保证了累加的效果,但set并不能保证按顺序执行。你可以直接用map.get("key").incrementAndGet()不需要再set,如果仅是体现累加效果,此时的map显得有点多余,用value自身就能解决问题 incrementAndGet()也是通过同步锁来实现的,所以如果你需要实现更复杂的逻辑,还是需要自己加同步锁的 另外,可以通过输出线程序号i与对应的value来观察线程的执行情况
睡觉了喂 2018-09-19
  • 打赏
  • 举报
回复
引用 10 楼 qq_35175535 的回复:
map.put("key", new AtomicInteger(key));你这么写当然不行。 AtomicInteger使用一个对象就行,把这句去掉
使用AtomicInteger.set()方法重新赋值,使用一个对象 执行10次的结果 ------1000------ ------2000------ ------3000------ ------4000------ ------5000------ ------5999------ ------6998------ ------7997------ ------8997------ ------9997------ 经过测试 , 误差减小 , 以10个一组有时候会全部正确 正确率约占每组80% , 正确的时候应该是每次在上次的基础上+1000 修改后完整代码如下

public static void main(String[] args) throws InterruptedException {

        ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<>();
        AtomicInteger value = new AtomicInteger(1);
        map.put("key", value);
        System.out.println("------" + map.get("key") + "------");

        ExecutorService executorService = Executors.newFixedThreadPool(1000);
        for (int i1 = 0; i1 < 10; i1++) {

            for (int i = 0; i < 1000; i++) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        value.set(map.get("key").incrementAndGet());
//                        map.put("key",value);
//                    System.out.println(key);
                    }
                });
            }
            Thread.sleep(3000); //模拟等待执行结束
//        Thread.yield();
            System.out.println("------" + map.get("key") + "------");
        }

        executorService.shutdown();
    }

睡觉了喂 2018-09-18
  • 打赏
  • 举报
回复
引用 7 楼 qq_35175535 的回复:
你换成AtomicInteger肯定是没问题的,至于输出不是正确的只可能是你主线程等待的时间太少了,你打印的时候任务还没执行完。再不是的话,你就得将你用AtomicInteger的代码贴出来
AtomicInteger的确是有问题的 , 线程也跑完了 , 因为每次自增后都会输出, 输出刚好1000次, 代码如下

public static void main(String[] args) throws InterruptedException {

        ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<>();
        map.put("key", new AtomicInteger(1));

        ExecutorService executorService = Executors.newFixedThreadPool(100);

        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    int key = map.get("key").incrementAndGet();
                    map.put("key", new AtomicInteger(key));
                    System.out.println(key);
                }
            });
        }
        Thread.sleep(3000); //模拟等待执行结束
//        Thread.yield();
        System.out.println("------" + map.get("key") + "------");

        executorService.shutdown();
    }


睡觉了喂 2018-09-18
  • 打赏
  • 举报
回复
引用 7 楼 qq_35175535 的回复:
你换成AtomicInteger肯定是没问题的,至于输出不是正确的只可能是你主线程等待的时间太少了,你打印的时候任务还没执行完。再不是的话,你就得将你用AtomicInteger的代码贴出来
AtomicInteger的确是有问题的 ,代码如下
wildyy 2018-09-18
  • 打赏
  • 举报
回复
你换成AtomicInteger肯定是没问题的,至于输出不是正确的只可能是你主线程等待的时间太少了,你打印的时候任务还没执行完。再不是的话,你就得将你用AtomicInteger的代码贴出来
wildyy 2018-09-18
  • 打赏
  • 举报
回复
map.put("key", new AtomicInteger(key));你这么写当然不行。
AtomicInteger使用一个对象就行,把这句去掉
睡觉了喂 2018-09-17
  • 打赏
  • 举报
回复
引用 4 楼 sanjuejianke 的回复:
ConcurrentHashMap的put方法是线程安全的,get也是线程安装的,但是组合起来不是,而你的代码逻辑就决定了get和put操作组合起来必须是原子操作。ConcurrentHashMap主要解决的问题是HashMap并发扩容的时候造成的闭环问题。
感谢, 除了加同步锁还有别的方法可以保证一致吗?
sjjk 2018-09-14
  • 打赏
  • 举报
回复
ConcurrentHashMap的put方法是线程安全的,get也是线程安装的,但是组合起来不是,而你的代码逻辑就决定了get和put操作组合起来必须是原子操作。ConcurrentHashMap主要解决的问题是HashMap并发扩容的时候造成的闭环问题。
睡觉了喂 2018-09-14
  • 打赏
  • 举报
回复
引用 1 楼 sanjuejianke 的回复:
int key = map.get("key") + 1; map.put("key", key); 这两行代码不是原子操作。 如果改成: synchronized (lock) { int key = map.get("key") + 1; map.put("key", key); System.out.println(key); } 这样就对了。
是的,我有试过加锁的确同步了, 你是说+1和put不是原子的话, 我换成AtomicInteger.incrementAndGet可以把+1做成同步的 但put还不是原子性的话就是说ConcurrentHashMap的put方法不能保证并发的读线程安全吗?
睡觉了喂 2018-09-14
  • 打赏
  • 举报
回复
是的,我有试过加锁的确同步了, 你是说+1和put不是原子的话,我换成AtomicInteger.incrementAndGet可以把
sjjk 2018-09-14
  • 打赏
  • 举报
回复
int key = map.get("key") + 1;
map.put("key", key);
这两行代码不是原子操作。
如果改成:
synchronized (lock) {
int key = map.get("key") + 1;
map.put("key", key);
System.out.println(key);
}
这样就对了。

51,397

社区成员

发帖
与我相关
我的任务
社区描述
Java相关技术讨论
javaspring bootspring cloud 技术论坛(原bbs)
社区管理员
  • Java相关社区
  • 小虚竹
  • 谙忆
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧