11

我正在尝试创建一个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);

如果没有,我该如何管理这样的事情?

4

5 回答 5

12

并发映射无助于代码的线程安全。您仍然可以获得比赛条件:

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对于并发读取也是线程安全的,因为该映射不再发生变异。

因此,您需要该类型的线程安全包装器,而不是原始类型的线程安全映射。java.util.concurrent.atomic如果需要任意类型,则可以从您自己的锁定容器中获取或滚动。

于 2012-08-27T12:09:15.573 回答
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();
于 2012-08-27T11:48:19.287 回答
3

最佳实践。您可以使用 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

于 2012-08-27T12:49:15.587 回答
1

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

于 2012-08-27T11:48:02.650 回答
0

您当前的代码会同时更改地图的值,因此这将不起作用。

如果多个线程可以将put值放入您的映射中,则您必须使用并发映射(如 ConcurrentHashMap)和非线程安全值(如Integer. ConcurrentMap.replace然后会做你想做的事(或AtomicInteger用来简化你的代码)。

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

于 2012-08-27T11:48:31.003 回答