18.2.6 集合线程安全解决方案
1. 线程安全问题概述
1.1 集合线程安全问题的根源
Java中的大多数集合类(如ArrayList、HashMap、HashSet等)都不是线程安全的,在多线程环境下使用会出现数据不一致、数据丢失、死循环等问题。
public class CollectionThreadSafetyDemo {
// 演示ArrayList的线程安全问题
public void demonstrateArrayListProblem() {
List<Integer> list = new ArrayList<>();
// 创建多个线程同时操作ArrayList
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
final int threadId = i;
executor.submit(() -> {
for (int j = 0; j < 1000; j++) {
list.add(threadId * 1000 + j);
}
});
}
executor.shutdown();
try {
executor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 期望大小:10 * 1000 = 10000
// 实际大小:可能小于10000,存在数据丢失
System.out.println("期望大小: 10000");
System.out.println("实际大小: " + list.size());
}
// 演示HashMap的线程安全问题
public void demonstrateHashMapProblem() {
Map<Integer, String> map = new HashMap<>();
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
final int threadId = i;
executor.submit(() -> {
for (int j = 0; j < 1000; j++) {
map.put(threadId * 1000 + j, "value" + (threadId * 1000 + j));
}
});
}
executor.shutdown();
try {
executor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("HashMap期望大小: 10000");
System.out.println("HashMap实际大小: " + map.size());
}
}
1.2 常见的线程安全问题
数据竞争 |
多线程同时修改共享数据 |
数据不一致 |
数据丢失 |
并发写入时数据被覆盖 |
数据缺失 |
死循环 |
HashMap扩容时链表成环 |
程序卡死 |
索引越界 |
ArrayList扩容时的并发问题 |
异常抛出 |
内存可见性 |
线程间数据不可见 |
数据不同步 |
2. 同步包装器解决方案
2.1 Collections.synchronizedXxx()方法
public class SynchronizedCollectionsDemo {
public void demonstrateSynchronizedCollections() {
// 1. 同步List
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// 2. 同步Set
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
// 3. 同步Map
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
// 基本操作是线程安全的
syncList.add("item1");
syncSet.add("element1");
syncMap.put("key1", 1);
System.out.println("同步集合创建完成");
}
// 同步集合的正确使用方式
public void correctUsageOfSynchronizedCollections() {
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// 错误:遍历时需要手动同步
// for (String item : syncList) { // 可能抛出ConcurrentModificationException
// System.out.println(item);
// }
// 正确:遍历时手动同步
synchronized (syncList) {
for (String item : syncList) {
System.out.println(item);
}
}
// 正确:复合操作需要手动同步
synchronized (syncList) {
if (!syncList.contains("item")) {
syncList.add("item");
}
}
}
// 同步集合性能测试
public void testSynchronizedCollectionPerformance() {
List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
List<Integer> normalList = new ArrayList<>();
int iterations = 1000000;
// 测试同步List性能
long startTime = System.nanoTime();
for (int i = 0; i < iterations; i++) {
syncList.add(i);
}
long syncTime = System.nanoTime() - startTime;
// 测试普通List性能
startTime = System.nanoTime();
for (int i = 0; i < iterations; i++) {
normalList.add(i);
}
long normalTime = System.nanoTime() - startTime;
System.out.println("同步List时间: " + syncTime / 1000000 + "ms");
System.out.println("普通List时间: " + normalTime / 1000000 + "ms");
System.out.println("性能损失: " + (syncTime - normalTime) * 100 / normalTime + "%");
}
}
2.2 同步包装器的局限性
public class SynchronizedCollectionLimitations {
// 局限性1: 遍历不安全
public void iterationProblem() {
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// 添加一些数据
for (int i = 0; i < 100; i++) {
syncList.add("item" + i);
}
// 启动一个线程不断修改集合
new Thread(() -> {
for (int i = 100; i < 200; i++) {
syncList.add("item" + i);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
// 主线程遍历(可能抛出ConcurrentModificationException)
try {
for (String item : syncList) {
System.out.println(item);
Thread.sleep(1); // 模拟处理时间
}
} catch (ConcurrentModificationException e) {
System.out.println("遍历时发生并发修改异常");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 局限性2: 复合操作不安全
public void compoundOperationProblem() {
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// 错误的复合操作
if (!syncList.contains("item")) { // 检查
syncList.add("item"); // 添加
}
// 在检查和添加之间,其他线程可能已经添加了"item"
// 正确的复合操作
synchronized (syncList) {
if (!syncList.contains("item")) {
syncList.add("item");
}
}
}
// 局限性3: 性能问题
public void performanceProblem() {
List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
// 所有操作都需要获取锁,即使是读操作
// 在高并发场景下性能较差
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
for (int j = 0; j < 10000; j++) {
syncList.add(j);
int size = syncList.size(); // 读操作也需要锁
}
});
}
executor.shutdown();
}
}
3. 并发集合解决方案
3.1 ConcurrentHashMap详解
public class ConcurrentHashMapDemo {
public void demonstrateConcurrentHashMap() {
// 创建ConcurrentHashMap
ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
// 基本操作
concurrentMap.put("key1", 1);
concurrentMap.put("key2", 2);
// 原子操作
concurrentMap.putIfAbsent("key3", 3);
concurrentMap.replace("key1", 1, 10);
// 批量操作
Map<String, Integer> batch = new HashMap<>();
batch.put("key4", 4);
batch.put("key5", 5);
concurrentMap.putAll(batch);
System.out.println("ConcurrentHashMap: " + concurrentMap);
}
// ConcurrentHashMap高级操作
public void advancedConcurrentHashMapOperations() {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 1. compute系列方法
map.compute("counter", (key, value) -> {
return value == null ? 1 : value + 1;
});
// 2. merge方法
map.merge("sum", 10, Integer::sum);
map.merge("sum", 20, Integer::sum); // 结果为30
// 3. forEach并行操作
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
map.for
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
Java面试圣经 文章被收录于专栏
Java面试圣经,带你练透java圣经
查看20道真题和解析