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圣经