多线程Map并发读后修改

两个线程同时操作一个全局map,如何保证值不被覆盖?

public static  Map<Integer, String> DATA_MAP = new ConcurrentHashMap<>(16);

DATA_MAP.put(3,"null,null");
  • 首先两个线程同时取Map的值
  • 使用当前时间分别替换掉value的两个null
String str = DATA_MAP.get(3);
Thread.sleep(1000);
String replaceStr = nextParam.replaceFirst("null",System.currentTimeMillis()+"");
DATA_MAP.put(nodeInfo.getNextNodeId(),replaceStr);

我现在得到的key为3的结果是:

20191233311231,null
//最后执行的会覆盖掉前一个执行的结果。 因为取值是得到的都是"null,null"

我想得到的结果是:

20191233311231,201912321321411

如何去做?

阅读 7.3k
5 个回答

可以上传一下完整的代码吗? 因为这里看不到另一个线程的操作。

ConcurrentHashMap是能保证一个写操作的的原子性,你这样先取出来,改完后put进去,结果肯定是这样。(一个读操作+一个写操作,ConcurrentHashMap不能保证原子性)

getput 的过程放在一起加一个锁,分开的操作不能保证原子性。

可以利用ConcurrentHashMapreplace方法,demo如下

public static void main(String[] args) throws ExecutionException, InterruptedException {
        ConcurrentHashMap<Integer, String> dataMap = new ConcurrentHashMap<>(16);
        dataMap.put(3, "null,null");
        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<Void> future1 = es.submit(new ReplaceNullCallable(3, dataMap));
        Future<Void> future2 = es.submit(new ReplaceNullCallable(3, dataMap));

        future1.get();
        future2.get();

        System.out.println("最终结果:" + dataMap.get(3));

    }


    public static class ReplaceNullCallable implements Callable<Void> {

        private int key;

        private ConcurrentHashMap<Integer, String> dataMap;

        public ReplaceNullCallable(int key, ConcurrentHashMap<Integer, String> dataMap) {
            this.key = key;
            this.dataMap = dataMap;
        }

        @Override
        public Void call() {
            while (true) {
                String oldValue = dataMap.get(key);
                if (!oldValue.contains("null")) {
                    break;
                }

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                String newValue = oldValue.replaceFirst("null", System.currentTimeMillis() + "");
                dataMap.replace(key, oldValue, newValue);
            }

            return null;
        }
    }

1、你用HashTable、Collections.synchronizedMap()、ConcurrentHashMap去存数据,而且你get和set在同一个synchronized里面。
2、再有,避免数据被覆盖,可以对key进行加锁,做一个加锁的方法,锁对象是操作的key,保证只有一个能操作,方法内部再用cas做一次判断,避免覆盖数据。

ConcurrentHashMap只能保证单个操作的原子性,你先get再put,整个操作不是原子性操作,因此是会出现覆盖的问题的。用java.util.concurrent.ConcurrentHashMap#replace(K, V, V)方法的话需要加上失败重试,不然在并发的情况下基本是会出现一个线程无法完成替换的情况的。

这个需求使用java.util.concurrent.ConcurrentHashMap#computeIfPresent方法实现起来会方便一点。
jdk7可能没有这个方法,需要jdk8或以上版本


    public static void main(String[] args) throws InterruptedException {
        final ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
        map.put(3, "null, null");
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        //模拟并发
        CountDownLatch latch = new CountDownLatch(1);
        Runnable replace = () -> {
            try {
                //模拟准备时间
                Thread.sleep(ThreadLocalRandom.current().nextLong(3000));
                //等待
                System.out.println(String.format("线程%s就绪@%d", Thread.currentThread().getId(), System.currentTimeMillis()));
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(String.format("线程%s替换前:%s", Thread.currentThread().getId(), map.get(3)));
            map.computeIfPresent(3, (k, v) -> v.replaceFirst("null", String.valueOf(System.currentTimeMillis())));
            System.out.println(String.format("线程%s替换后:%s", Thread.currentThread().getId(), map.get(3)));
        };

        executorService.execute(replace);
        executorService.execute(replace);
        //等待准备完成
        Thread.sleep(4000L);
        latch.countDown();
        //等待完成替换
        Thread.sleep(300L);
        System.out.println(map.get(3));
        executorService.shutdown();
    }
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题