我正在尝试创建一个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);
如果没有,我该怎么办?
答案 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()
例如。