java map并发更新

时间:2012-08-27 11:43:30

标签: java concurrency map

我正在尝试创建一个Map int值并通过多个线程增加它们。两个或多个线程可能会增加相同的密钥。

ConcurrentHashMap文件对我来说非常不清楚,因为它是:

Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove)

我想知道使用ConcurrentHashMap的以下代码是否正常

myMap.put(X, myMap.get(X) + 1);

如果没有,我该怎么办?

5 个答案:

答案 0 :(得分:9)

并发映射不会帮助您解决代码的线程安全问题。你仍然可以获得竞争条件:

Thread-1: x = 1, get(x)
Thread-2: x = 1, get(x)
Thread-1: put(x + 1) => 2
Thread-2: put(x + 1) => 2

发生了两次增量,但你仍然只获得+1。只有当您的目标是修改地图本身而不是其内容时,才需要并发地图。即使是最简单的HashMap也是threadsafe for concurrent reads,因为地图不再发生变异。

因此,您需要一个针对该类型的线程安全包装,而不是基本类型的线程安全映射。如果需要任意类型,可以来自java.util.concurrent.atomic或滚动您自己的锁定容器。

答案 1 :(得分:3)

一个想法是将ConcurrentMap与AtomicInteger相结合,后者有一个增量方法。

 AtomicInteger current = map.putIfAbsent(key, new AtomicInteger(1));
 int newValue = current == null ? 1 :current.incrementAndGet();

或(更有效地,感谢@Keppil)使用额外的代码保护来避免不必要的对象创建:

 AtomicInteger current = map.get(key);
 if (current == null){
     current = map.putIfAbsent(key, new AtomicInteger(1));
 }
 int newValue = current == null ? 1 : current.incrementAndGet();

答案 2 :(得分:2)

最佳做法。您可以使用HashMap和AtomicInteger。 测试代码:

public class HashMapAtomicIntegerTest {
    public static final int KEY = 10;

    public static void main(String[] args) {
        HashMap<Integer, AtomicInteger> concurrentHashMap = new HashMap<Integer, AtomicInteger>();
        concurrentHashMap.put(HashMapAtomicIntegerTest.KEY, new AtomicInteger());
        List<HashMapAtomicCountThread> threadList = new ArrayList<HashMapAtomicCountThread>();
        for (int i = 0; i < 500; i++) {
            HashMapAtomicCountThread testThread = new HashMapAtomicCountThread(
                    concurrentHashMap);
            testThread.start();
            threadList.add(testThread);
        }
        int index = 0;
        while (true) {
            for (int i = index; i < 500; i++) {
                HashMapAtomicCountThread testThread = threadList.get(i);
                if (testThread.isAlive()) {
                    break;
                } else {
                    index++;
                }
            }
            if (index == 500) {
                break;
            }
        }
        System.out.println("The result value should be " + 5000000
                + ",actually is"
                + concurrentHashMap.get(HashMapAtomicIntegerTest.KEY));
    }
}

class HashMapAtomicCountThread extends Thread {
    HashMap<Integer, AtomicInteger> concurrentHashMap = null;

    public HashMapAtomicCountThread(
            HashMap<Integer, AtomicInteger> concurrentHashMap) {
        this.concurrentHashMap = concurrentHashMap;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            concurrentHashMap.get(HashMapAtomicIntegerTest.KEY)
                    .getAndIncrement();
        }
    }
}

结果:

  

结果值应为5000000,实际为5000000

或者HashMap和synchronized,但比前者慢得多

public class HashMapSynchronizeTest {

    public static final int KEY = 10;

    public static void main(String[] args) {

        HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>();
        hashMap.put(KEY, 0);
        List<HashMapSynchronizeThread> threadList = new ArrayList<HashMapSynchronizeThread>();
        for (int i = 0; i < 500; i++) {
            HashMapSynchronizeThread testThread = new HashMapSynchronizeThread(
                    hashMap);
            testThread.start();
            threadList.add(testThread);
        }
        int index = 0;
        while (true) {
            for (int i = index; i < 500; i++) {
                HashMapSynchronizeThread testThread = threadList.get(i);
                if (testThread.isAlive()) {
                    break;
                } else {
                    index++;
                }
            }
            if (index == 500) {
                break;
            }
        }
        System.out.println("The result value should be " + 5000000
                + ",actually is" + hashMap.get(KEY));
    }
}

class HashMapSynchronizeThread extends Thread {
    HashMap<Integer, Integer> hashMap = null;

    public HashMapSynchronizeThread(
            HashMap<Integer, Integer> hashMap) {
        this.hashMap = hashMap;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            synchronized (hashMap) {
                hashMap.put(HashMapSynchronizeTest.KEY,
                        hashMap
                                .get(HashMapSynchronizeTest.KEY) + 1);
            }
        }
    }
}

结果:

  

结果值应为5000000,实际为5000000

使用ConcurrentHashMap会得到错误的结果。

public class ConcurrentHashMapTest {

    public static final int KEY = 10;

    public static void main(String[] args) {
        ConcurrentHashMap<Integer, Integer> concurrentHashMap = new ConcurrentHashMap<Integer, Integer>();
        concurrentHashMap.put(KEY, 0);
        List<CountThread> threadList = new ArrayList<CountThread>();
        for (int i = 0; i < 500; i++) {
            CountThread testThread = new CountThread(concurrentHashMap);
            testThread.start();
            threadList.add(testThread);
        }
        int index = 0;
        while (true) {
            for (int i = index; i < 500; i++) {
                CountThread testThread = threadList.get(i);
                if (testThread.isAlive()) {
                    break;
                } else {
                    index++;
                }
            }
            if (index == 500) {
                break;
            }
        }
        System.out.println("The result value should be " + 5000000
                + ",actually is" + concurrentHashMap.get(KEY));
    }
}

class CountThread extends Thread {
    ConcurrentHashMap<Integer, Integer> concurrentHashMap = null;

    public CountThread(ConcurrentHashMap<Integer, Integer> concurrentHashMap) {
        this.concurrentHashMap = concurrentHashMap;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            concurrentHashMap.put(ConcurrentHashMapTest.KEY,
                    concurrentHashMap.get(ConcurrentHashMapTest.KEY) + 1);
        }
    }
}

结果:

  

结果值应为5000000,实际上为11759

答案 3 :(得分:1)

您可以将操作放在synchronized (myMap) {...}块中。

答案 4 :(得分:0)

您当前的代码会同时更改地图的值,因此无法使用。

如果多个线程可以put为您的映射值,则必须使用ConcurrentHashMap等并发映射和非线程安全值,如Integer。然后ConcurrentMap.replace会按照您的意愿行事(或使用AtomicInteger来简化您的代码。)

如果您的线程只会更改地图的值(而不是添加/更改键),那么您可以使用标准地图存储线程安全值 AtomicInteger。然后你的线程会调用:map.get(key).incrementAndGet()例如。