Mor*_*lau 2 java concurrency multithreading hashmap thread-safety
关于这个话题有很多讨论,例如这里:
ConcurrentHashMap 和 Collections.synchronizedMap(Map) 有什么区别?
但我还没有找到我的特定用例的答案。
通常,您不能假设 HashMap 是线程安全的。如果同时从不同的线程写入相同的密钥,一切都可能会崩溃。但是如果我知道我的所有线程都有唯一的键呢?
这段代码是线程安全的还是我需要添加阻塞机制(或使用并发映射)?
Map<int, String> myMap = new HashMap<>();
for (int i = 1 ; i > 6 ; i++) {
new Thread(() -> {
myMap.put(i, Integer.toString(i));
}).start();
}
Run Code Online (Sandbox Code Playgroud)
答案很简单:HashMap完全没有线程安全保证。
事实上,它明确记录它不是线程安全的:
如果多个线程并发访问一个散列映射,并且至少有一个线程在结构上修改了映射,则必须在外部进行同步。
因此,在没有任何同步的情况下从多个线程访问一个是灾难性的。
我已经看到,每个线程使用不同的密钥原因要点(如在迭代产生无限循环同时发生)的情况。
想想重新散列:当达到阈值时,需要调整内部存储桶数组的大小。这是一个有点冗长的操作(与单个 相比put)。在此期间,如果另一个线程也尝试这样put做(甚至可能触发第二次重新散列!),就会发生各种奇怪的事情。
此外,没有可靠的方法来证明您的特定用例是安全的,因为您可以运行的所有测试可能只是“意外”工作。换句话说:你永远不能依赖这个工作,即使你认为你用单元测试覆盖了它。
由于不是每个人都相信,您可以使用以下代码轻松地自行测试:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class HashMapDemonstration {
public static void main(String[] args) throws InterruptedException {
int threadCount = 10;
int valuesPerThread = 1000;
Map<Integer, Integer> map = new HashMap<>();
List<Thread> threads = new ArrayList<>(threadCount);
for (int i = 0; i < threadCount; i++) {
Thread thread = new Thread(new MyUpdater(map, i*valuesPerThread, (i+1)*valuesPerThread - 1));
thread.start();
threads.add(thread);
}
for (Thread thread : threads) {
thread.join();
}
System.out.printf("%d threads with %d values per thread with a %s produced %d entries, should be %d%n",
threadCount, valuesPerThread, map.getClass().getName(), map.size(), threadCount * valuesPerThread);
}
}
class MyUpdater implements Runnable {
private final Map<Integer, Integer> map;
private final int startValue;
private final int endValue;
MyUpdater(Map<Integer, Integer> map, int startValue, int endValue) {
this.map = map;
this.startValue = startValue;
this.endValue = endValue;
System.out.printf("Creating updater for values %d to %d%n", startValue, endValue);
}
@Override
public void run() {
for (int i = startValue; i<= endValue; i++) {
map.put(i, i);
}
}
}
Run Code Online (Sandbox Code Playgroud)
这正是 OP 提到的程序类型:每个线程只会写入其他线程从未接触过的键。而且,结果Map不会包含所有条目:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class HashMapDemonstration {
public static void main(String[] args) throws InterruptedException {
int threadCount = 10;
int valuesPerThread = 1000;
Map<Integer, Integer> map = new HashMap<>();
List<Thread> threads = new ArrayList<>(threadCount);
for (int i = 0; i < threadCount; i++) {
Thread thread = new Thread(new MyUpdater(map, i*valuesPerThread, (i+1)*valuesPerThread - 1));
thread.start();
threads.add(thread);
}
for (Thread thread : threads) {
thread.join();
}
System.out.printf("%d threads with %d values per thread with a %s produced %d entries, should be %d%n",
threadCount, valuesPerThread, map.getClass().getName(), map.size(), threadCount * valuesPerThread);
}
}
class MyUpdater implements Runnable {
private final Map<Integer, Integer> map;
private final int startValue;
private final int endValue;
MyUpdater(Map<Integer, Integer> map, int startValue, int endValue) {
this.map = map;
this.startValue = startValue;
this.endValue = endValue;
System.out.printf("Creating updater for values %d to %d%n", startValue, endValue);
}
@Override
public void run() {
for (int i = startValue; i<= endValue; i++) {
map.put(i, i);
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,Map每次运行的决赛中的实际条目数会有所不同。它甚至有时会打印10000(因为它不是线程安全的!)。
请注意,这种失败模式(丢失条目)绝对不是唯一可能的:基本上任何事情都可能发生。
| 归档时间: |
|
| 查看次数: |
49 次 |
| 最近记录: |