Skip to main content

Java 并发容器详解

Java并发包提供了多种线程安全的容器类,这些容器专门为多线程环境设计,提供了比使用synchronized更高效的并发访问机制。本文将详细介绍各种并发容器的使用方法和最佳实践。

核心价值

并发容器 = 线程安全性 + 高性能访问 + 低竞争设计 + 功能扩展 + 便捷API

  • 🛡️ 线程安全:无需额外同步,免除并发错误困扰
  • 高性能:采用分段锁、无锁算法等现代并发技术
  • 🔄 一致性模型:提供清晰的一致性保证
  • 🚀 扩展性强:良好应对高并发负载场景
  • 🔧 功能丰富:提供阻塞、超时等特性满足多样需求

1. 并发容器概述

1.1 什么是并发容器?

核心概念

并发容器是Java并发包中提供的线程安全集合类,它们通过不同的并发控制机制(如分段锁、CAS操作、读写锁等)实现线程安全,避免了使用synchronized的性能开销。

1.2 并发容器与传统同步容器对比

特性传统同步容器并发容器
实现方式Collections.synchronizedXxxjava.util.concurrent
同步机制方法级synchronized锁分段锁、CAS、读写分离等
锁粒度粗粒度(整个容器)细粒度(部分数据)
性能高并发下性能较差高并发下性能更好
迭代器fail-fast机制弱一致性或快照迭代
阻塞操作不支持部分容器支持
原子复合操作需要额外同步部分容器原生支持

1.3 并发容器分类

并发Map实现类

  • ConcurrentHashMap:分段锁实现的高性能线程安全哈希表
  • ConcurrentSkipListMap:基于跳表的有序并发Map,适合高并发读取
java
1// 创建并发Map
2Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
3// 线程安全操作
4concurrentMap.put("one", 1);
5concurrentMap.putIfAbsent("two", 2); // 原子性的"如果不存在则放入"
6// 复合操作
7concurrentMap.compute("three", (k, v) -> (v == null) ? 3 : v + 1);

2. ConcurrentHashMap详解

2.1 ConcurrentHashMap 原理

内部结构与工作原理

JDK 7实现

  • 使用分段锁(Segment)机制,将数据分为多个段
  • 每个段独立加锁,减少锁竞争
  • Segment继承自ReentrantLock

JDK 8+实现

  • 移除Segment概念,采用Node数组+链表+红黑树结构
  • 使用CAS操作和synchronized实现更细粒度锁
  • 当链表长度超过阈值(8)时转换为红黑树,提升性能
  • 对桶(bucket)级别加锁,进一步减少锁竞争

2.2 基本用法

java
1import java.util.concurrent.ConcurrentHashMap;
2
3// 创建ConcurrentHashMap
4ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
5
6// 添加元素
7map.put("apple", 10);
8map.put("banana", 20);
9
10// 获取元素
11int appleCount = map.get("apple"); // 10
12
13// 原子性的"如果不存在则添加"
14map.putIfAbsent("orange", 15); // 添加成功,返回null
15map.putIfAbsent("apple", 25); // 添加失败,返回10
16
17// 移除元素
18map.remove("banana"); // 返回20
19
20// 原子性的"如果值匹配则移除"
21boolean removed = map.remove("apple", 10); // 返回true,移除成功

2.3 性能特性与最佳实践

ConcurrentHashMap的性能特点:

  1. 读操作完全并行:多线程可以同时读取,无阻塞
  2. 写操作局部锁定:仅锁定需要修改的部分,允许其他部分并发访问
  3. 弱一致性迭代器:迭代时不会抛出ConcurrentModificationException,但可能不反映最新修改
  4. 高度优化的并发访问:使用内部优化减少锁竞争
  5. 调整大小操作并发化:多个线程可同时参与扩容过程

3. CopyOnWriteArrayList 详解

3.1 写时复制机制

CopyOnWriteArrayList工作原理

CopyOnWriteArrayList的核心特性

  1. 写时复制:每次修改操作都会创建底层数组的新副本
  2. 读操作无锁:读取操作不需要加锁,提供了最大程度的并发读取性能
  3. 写操作同步:写操作需要获取独占锁,一次只能有一个线程修改
  4. 适用场景:读多写少的场景,写操作频繁的场景性能较差
  5. 内存开销:每次修改都创建新数组,可能导致GC压力和内存使用增加

3.2 基本用法

java
1import java.util.concurrent.CopyOnWriteArrayList;
2
3// 创建CopyOnWriteArrayList
4List<String> cowList = new CopyOnWriteArrayList<>();
5
6// 添加元素
7cowList.add("Java");
8cowList.add("Python");
9cowList.addAll(Arrays.asList("Go", "Rust"));
10
11// 获取元素
12String language = cowList.get(0); // "Java"
13
14// 迭代 - 安全,不会抛出ConcurrentModificationException
15for (String lang : cowList) {
16 System.out.println(lang);
17 // 即使此处修改cowList,迭代器仍然基于原始快照
18 cowList.add("新语言"); // 不会影响当前迭代
19}
20
21// 修改元素
22cowList.set(1, "Python 3");
23
24// 删除元素
25cowList.remove("Go");
26cowList.remove(0); // 删除第一个元素

3.3 适用场景与最佳实践

java
1// 场景:事件监听器列表
2public class EventManager {
3 // 使用CopyOnWriteArrayList存储监听器 - 非常适合监听器模式
4 private final List<EventListener> listeners = new CopyOnWriteArrayList<>();
5
6 // 添加监听器 - 写操作,相对不频繁
7 public void addListener(EventListener listener) {
8 listeners.add(listener);
9 }
10
11 // 移除监听器 - 写操作,相对不频繁
12 public void removeListener(EventListener listener) {
13 listeners.remove(listener);
14 }
15
16 // 触发事件 - 读操作,频繁执行
17 public void fireEvent(Event event) {
18 // 安全迭代,即使有线程同时添加/移除监听器
19 for (EventListener listener : listeners) {
20 listener.onEvent(event);
21 }
22 }
23}
性能注意事项

CopyOnWriteArrayList 适用于读操作远多于写操作的场景。对于频繁写入的场景,它的性能会显著下降,因为:

  1. 每次写操作都会复制整个底层数组
  2. 写操作需要获取独占锁,导致写操作串行化
  3. 内存使用率高,可能增加GC压力

在元素数量较大且修改频繁的场景,应考虑使用其他并发容器。

4. 阻塞队列

4.1 BlockingQueue 接口

阻塞队列的核心操作对比
操作类型抛出异常返回特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e, time, unit)
移除remove()poll()take()poll(time, unit)
检查element()peek()不适用不适用

操作行为说明

  • 抛出异常:队列满/空时抛出异常
  • 返回特殊值:队列满返回false,队列空返回null
  • 阻塞:队列满/空时阻塞等待
  • 超时:阻塞指定时间后仍无法操作则返回特殊值
java
1import java.util.concurrent.*;
2
3public class BlockingQueueExample {
4 public static void main(String[] args) throws InterruptedException {
5 // 创建有界阻塞队列 - 容量为5
6 BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
7
8 // 添加元素 - 多种方式
9 queue.add("元素1"); // 成功添加,队列未满
10 queue.offer("元素2"); // 成功添加,返回true
11 queue.put("元素3"); // 添加元素,可能阻塞
12 queue.offer("元素4", 1, TimeUnit.SECONDS); // 添加元素,最多等待1秒
13
14 // 检索元素但不移除
15 String peek = queue.peek(); // 查看队首元素
16 System.out.println("队首元素: " + peek);
17
18 // 移除元素 - 多种方式
19 String item1 = queue.remove(); // 移除并返回队首元素,队列为空时抛异常
20 String item2 = queue.poll(); // 移除并返回队首元素,队列为空时返回null
21 String item3 = queue.take(); // 移除并返回队首元素,队列为空时阻塞
22 String item4 = queue.poll(1, TimeUnit.SECONDS); // 移除元素,最多等待1秒
23
24 System.out.println("已移除: " + item1 + ", " + item2 + ", " + item3 + ", " + item4);
25
26 // 检查队列状态
27 System.out.println("队列是否为空: " + queue.isEmpty());
28 System.out.println("队列元素数量: " + queue.size());
29 System.out.println("队列是否包含'元素1': " + queue.contains("元素1"));
30 }
31}

4.2 ArrayBlockingQueue

ArrayBlockingQueue特点

核心特性

  • 基于数组实现的有界阻塞队列
  • 创建时必须指定容量
  • 按照FIFO(先进先出)原则对元素进行排序
  • 使用单一的锁来控制对队列的访问
  • 支持公平策略

适用场景

  • 明确知道队列大小上限的场景
  • 需要FIFO顺序的场景
  • 生产者和消费者速度相近的情况
  • 需要限制系统资源使用的情况

4.3 LinkedBlockingQueue

LinkedBlockingQueue特点

核心特性

  • 基于链表实现的可选有界阻塞队列
  • 默认容量为Integer.MAX_VALUE(可视为无界)
  • 可以指定容量使其成为有界队列
  • 使用两个锁(takeLock和putLock)分别控制入队和出队操作
  • 相比ArrayBlockingQueue,并发性能更好

适用场景

  • 不确定队列大小上限的场景
  • 生产者和消费者速度差异较大的场景
  • 需要更高并发吞吐量的场景
java
1// 创建无界LinkedBlockingQueue
2BlockingQueue<String> unboundedQueue = new LinkedBlockingQueue<>();
3
4// 创建有界LinkedBlockingQueue(容量1000)
5BlockingQueue<String> boundedQueue = new LinkedBlockingQueue<>(1000);
6
7// 两个独立的锁提高并发性
8// putLock控制入队
9// takeLock控制出队
10// 两个操作可以并发执行

4.4 DelayQueue

DelayQueue特点

核心特性

  • 无界阻塞延迟队列
  • 元素只有在其指定的延迟时间到期后才能被取出
  • 队列头部是延迟最先到期的元素
  • 元素必须实现Delayed接口
  • 如果没有元素到期,take()方法会阻塞

适用场景

  • 定时任务调度
  • 缓存过期策略实现
  • 请求超时处理
  • 限流算法实现
java
1import java.util.concurrent.*;
2
3public class DelayQueueExample {
4 public static void main(String[] args) throws InterruptedException {
5 // 创建DelayQueue
6 DelayQueue<DelayedTask> delayQueue = new DelayQueue<>();
7
8 // 添加延迟任务(当前时间 + 指定延迟)
9 long now = System.currentTimeMillis();
10 delayQueue.put(new DelayedTask("Task1", now + 2000)); // 延迟2秒
11 delayQueue.put(new DelayedTask("Task2", now + 5000)); // 延迟5秒
12 delayQueue.put(new DelayedTask("Task3", now + 1000)); // 延迟1秒
13 delayQueue.put(new DelayedTask("Task4", now + 3000)); // 延迟3秒
14
15 System.out.println("所有任务已添加到队列");
16
17 // 按延迟时间顺序取出任务执行
18 while (!delayQueue.isEmpty()) {
19 // take() 会一直阻塞直到有可用元素
20 DelayedTask task = delayQueue.take();
21 System.out.println(System.currentTimeMillis() - now +
22 "ms 后执行: " + task);
23 }
24 }
25
26 // 实现Delayed接口的任务类
27 static class DelayedTask implements Delayed {
28 private final String name;
29 private final long executeTime;
30
31 public DelayedTask(String name, long executeTime) {
32 this.name = name;
33 this.executeTime = executeTime;
34 }
35
36 @Override
37 public long getDelay(TimeUnit unit) {
38 // 返回剩余延迟时间
39 return unit.convert(executeTime - System.currentTimeMillis(),
40 TimeUnit.MILLISECONDS);
41 }
42
43 @Override
44 public int compareTo(Delayed other) {
45 // 比较剩余延迟时间
46 if (other == this) {
47 return 0;
48 }
49
50 if (other instanceof DelayedTask) {
51 DelayedTask otherTask = (DelayedTask) other;
52 return Long.compare(this.executeTime, otherTask.executeTime);
53 }
54
55 return Long.compare(this.getDelay(TimeUnit.MILLISECONDS),
56 other.getDelay(TimeUnit.MILLISECONDS));
57 }
58
59 @Override
60 public String toString() {
61 return name;
62 }
63 }
64}

4.5 SynchronousQueue

SynchronousQueue特点

核心特性

  • 特殊的阻塞队列,内部容量为零
  • 不存储元素,每个插入操作必须等待对应的移除操作
  • 直接从生产者传递给消费者("直通车"队列)
  • 支持公平和非公平两种模式
  • 适用于"交接"场景

适用场景

  • 需要即时交付任务的场景
  • 生产者和消费者需要"手递手"交接的场景
  • Executors.newCachedThreadPool()使用SynchronousQueue作为工作队列
java
1// SynchronousQueue示例
2public class SynchronousQueueExample {
3 public static void main(String[] args) {
4 // 创建SynchronousQueue (默认非公平模式)
5 BlockingQueue<String> syncQueue = new SynchronousQueue<>();
6 // 也可以指定为公平模式: new SynchronousQueue<>(true);
7
8 // 消费者线程
9 new Thread(() -> {
10 try {
11 // 模拟消费者延迟
12 Thread.sleep(2000);
13
14 // 取元素
15 System.out.println("消费者准备取元素: " + System.currentTimeMillis());
16 String item = syncQueue.take();
17 System.out.println("消费者已取到元素: " + item + " - " + System.currentTimeMillis());
18
19 // 再次取元素
20 Thread.sleep(2000);
21 System.out.println("消费者再次准备取元素: " + System.currentTimeMillis());
22 item = syncQueue.take();
23 System.out.println("消费者已取到元素: " + item + " - " + System.currentTimeMillis());
24 } catch (InterruptedException e) {
25 Thread.currentThread().interrupt();
26 }
27 }).start();
28
29 // 生产者线程
30 new Thread(() -> {
31 try {
32 // 存入元素
33 System.out.println("生产者准备放入元素: " + System.currentTimeMillis());
34 syncQueue.put("元素A");
35 System.out.println("生产者已放入元素A - " + System.currentTimeMillis());
36
37 // 再次存入元素
38 System.out.println("生产者准备放入元素: " + System.currentTimeMillis());
39 syncQueue.put("元素B");
40 System.out.println("生产者已放入元素B - " + System.currentTimeMillis());
41 } catch (InterruptedException e) {
42 Thread.currentThread().interrupt();
43 }
44 }).start();
45 }
46}

5. ConcurrentLinkedQueue详解

5.1 ConcurrentLinkedQueue 基本概念

ConcurrentLinkedQueue是一个无界线程安全的队列,基于链表实现。

ConcurrentLinkedQueue基本用法示例
java
1import java.util.concurrent.ConcurrentLinkedQueue;
2import java.util.concurrent.ExecutorService;
3import java.util.concurrent.Executors;
4
5public class ConcurrentLinkedQueueExamples {
6
7 /**
8 * ConcurrentLinkedQueue基本用法
9 */
10 public static class BasicUsage {
11 public static void main(String[] args) {
12 ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
13 ExecutorService executor = Executors.newFixedThreadPool(4);
14
15 System.out.println("=== ConcurrentLinkedQueue基本用法 ===");
16
17 // 多个生产者
18 for (int i = 0; i < 2; i++) {
19 final int producerId = i;
20 executor.submit(() -> {
21 for (int j = 0; j < 5; j++) {
22 String item = "Producer" + producerId + "-Item" + j;
23 queue.offer(item);
24 System.out.println("生产者" + producerId + "放入: " + item);
25 try {
26 Thread.sleep(200);
27 } catch (InterruptedException e) {
28 Thread.currentThread().interrupt();
29 }
30 }
31 });
32 }
33
34 // 多个消费者
35 for (int i = 0; i < 2; i++) {
36 final int consumerId = i;
37 executor.submit(() -> {
38 for (int j = 0; j < 5; j++) {
39 String item = queue.poll();
40 if (item != null) {
41 System.out.println("消费者" + consumerId + "取出: " + item);
42 }
43 try {
44 Thread.sleep(300);
45 } catch (InterruptedException e) {
46 Thread.currentThread().interrupt();
47 }
48 }
49 });
50 }
51
52 executor.shutdown();
53 }
54 }
55
56 /**
57 * ConcurrentLinkedQueue实际应用场景
58 */
59 public static class PracticalApplications {
60
61 /**
62 * 消息队列实现
63 */
64 public static class MessageQueue {
65 private final ConcurrentLinkedQueue<Message> queue = new ConcurrentLinkedQueue<>();
66
67 public void sendMessage(Message message) {
68 queue.offer(message);
69 }
70
71 public Message receiveMessage() {
72 return queue.poll();
73 }
74
75 public boolean isEmpty() {
76 return queue.isEmpty();
77 }
78
79 public int size() {
80 return queue.size();
81 }
82
83 static class Message {
84 private final String id;
85 private final String content;
86 private final long timestamp;
87
88 public Message(String id, String content) {
89 this.id = id;
90 this.content = content;
91 this.timestamp = System.currentTimeMillis();
92 }
93
94 @Override
95 public String toString() {
96 return "Message{id='" + id + "', content='" + content + "', timestamp=" + timestamp + "}";
97 }
98 }
99 }
100 }
101}

6. 其他并发容器

6.1 ConcurrentSkipListMap

ConcurrentSkipListMap基本用法示例
java
1import java.util.concurrent.ConcurrentSkipListMap;
2
3public class ConcurrentSkipListMapExamples {
4
5 /**
6 * ConcurrentSkipListMap基本用法
7 */
8 public static class BasicUsage {
9 public static void main(String[] args) {
10 ConcurrentSkipListMap<String, Integer> map = new ConcurrentSkipListMap<>();
11
12 System.out.println("=== ConcurrentSkipListMap基本用法 ===");
13
14 // 添加元素(自动排序)
15 map.put("zebra", 1);
16 map.put("apple", 2);
17 map.put("banana", 3);
18 map.put("cat", 4);
19
20 System.out.println("排序后的Map:");
21 map.forEach((key, value) -> System.out.println(key + " = " + value));
22
23 // 获取第一个和最后一个元素
24 System.out.println("第一个元素: " + map.firstKey());
25 System.out.println("最后一个元素: " + map.lastKey());
26
27 // 获取子Map
28 System.out.println("a到c之间的元素:");
29 map.subMap("a", "d").forEach((key, value) ->
30 System.out.println(key + " = " + value));
31 }
32 }
33}

6.2 ConcurrentSkipListSet

ConcurrentSkipListSet基本用法示例
java
1import java.util.concurrent.ConcurrentSkipListSet;
2
3public class ConcurrentSkipListSetExamples {
4
5 /**
6 * ConcurrentSkipListSet基本用法
7 */
8 public static class BasicUsage {
9 public static void main(String[] args) {
10 ConcurrentSkipListSet<String> set = new ConcurrentSkipListSet<>();
11
12 System.out.println("=== ConcurrentSkipListSet基本用法 ===");
13
14 // 添加元素(自动排序)
15 set.add("zebra");
16 set.add("apple");
17 set.add("banana");
18 set.add("cat");
19
20 System.out.println("排序后的Set:");
21 set.forEach(System.out::println);
22
23 // 获取第一个和最后一个元素
24 System.out.println("第一个元素: " + set.first());
25 System.out.println("最后一个元素: " + set.last());
26
27 // 获取子Set
28 System.out.println("a到c之间的元素:");
29 set.subSet("a", "d").forEach(System.out::println);
30 }
31 }
32}

7. 性能比较

7.1 不同容器的性能特点

容器性能比较示例
java
1import java.util.*;
2import java.util.concurrent.*;
3
4public class ContainerPerformanceComparison {
5
6 /**
7 * 容器性能比较
8 */
9 public static void main(String[] args) {
10 System.out.println("=== 容器性能比较 ===");
11
12 // HashMap vs ConcurrentHashMap
13 System.out.println("=== HashMap vs ConcurrentHashMap ===");
14
15 // HashMap(非线程安全)
16 Map<String, Integer> hashMap = new HashMap<>();
17 long start = System.currentTimeMillis();
18 for (int i = 0; i < 100000; i++) {
19 hashMap.put("key" + i, i);
20 }
21 System.out.println("HashMap写入时间: " + (System.currentTimeMillis() - start) + "ms");
22
23 // ConcurrentHashMap(线程安全)
24 Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
25 start = System.currentTimeMillis();
26 for (int i = 0; i < 100000; i++) {
27 concurrentHashMap.put("key" + i, i);
28 }
29 System.out.println("ConcurrentHashMap写入时间: " + (System.currentTimeMillis() - start) + "ms");
30
31 // ArrayList vs CopyOnWriteArrayList
32 System.out.println("\n=== ArrayList vs CopyOnWriteArrayList ===");
33
34 // ArrayList(非线程安全)
35 List<String> arrayList = new ArrayList<>();
36 start = System.currentTimeMillis();
37 for (int i = 0; i < 10000; i++) {
38 arrayList.add("item" + i);
39 }
40 System.out.println("ArrayList写入时间: " + (System.currentTimeMillis() - start) + "ms");
41
42 // CopyOnWriteArrayList(线程安全)
43 List<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
44 start = System.currentTimeMillis();
45 for (int i = 0; i < 10000; i++) {
46 copyOnWriteArrayList.add("item" + i);
47 }
48 System.out.println("CopyOnWriteArrayList写入时间: " + (System.currentTimeMillis() - start) + "ms");
49 }
50}

8. 最佳实践

8.1 选择合适的容器

容器选择指南示例
java
1public class ContainerSelectionGuide {
2
3 /**
4 * 容器选择指南
5 */
6 public static void selectionGuide() {
7 System.out.println("=== 并发容器选择指南 ===");
8
9 // 高并发读写 - 使用ConcurrentHashMap
10 System.out.println("1. 高并发读写 -> ConcurrentHashMap");
11 System.out.println(" 适用场景:缓存、计数器、配置管理");
12
13 // 读多写少 - 使用CopyOnWriteArrayList
14 System.out.println("2. 读多写少 -> CopyOnWriteArrayList");
15 System.out.println(" 适用场景:监听器列表、配置列表");
16
17 // 生产者消费者 - 使用BlockingQueue
18 System.out.println("3. 生产者消费者 -> BlockingQueue");
19 System.out.println(" 适用场景:任务队列、消息队列");
20
21 // 需要排序 - 使用ConcurrentSkipListMap
22 System.out.println("4. 需要排序 -> ConcurrentSkipListMap");
23 System.out.println(" 适用场景:有序缓存、排行榜");
24
25 // 简单同步 - 使用Collections.synchronizedXXX()
26 System.out.println("5. 简单同步 -> Collections.synchronizedXXX()");
27 System.out.println(" 适用场景:低并发场景");
28 }
29
30 // 高并发读写 - 使用ConcurrentHashMap
31 public static <K, V> Map<K, V> createHighConcurrencyMap() {
32 return new ConcurrentHashMap<>();
33 }
34
35 // 读多写少 - 使用CopyOnWriteArrayList
36 public static <T> List<T> createReadHeavyList() {
37 return new CopyOnWriteArrayList<>();
38 }
39
40 // 生产者消费者 - 使用BlockingQueue
41 public static <T> BlockingQueue<T> createProducerConsumerQueue() {
42 return new LinkedBlockingQueue<>();
43 }
44
45 // 需要排序 - 使用ConcurrentSkipListMap
46 public static <K extends Comparable<K>, V> Map<K, V> createSortedMap() {
47 return new ConcurrentSkipListMap<>();
48 }
49}

8.2 避免常见陷阱

常见陷阱示例
java
1public class CommonPitfalls {
2
3 /**
4 * 避免常见陷阱
5 */
6 public static void avoidPitfalls() {
7 System.out.println("=== 避免常见陷阱 ===");
8
9 // 错误:在迭代时修改集合
10 System.out.println("1. 避免在迭代时修改集合");
11 List<String> list = new CopyOnWriteArrayList<>();
12 list.add("a");
13 list.add("b");
14 list.add("c");
15
16 // 这样是安全的,因为CopyOnWriteArrayList在迭代时创建副本
17 for (String item : list) {
18 list.add("new"); // 不会影响当前迭代
19 }
20
21 // 正确:使用原子操作
22 System.out.println("2. 使用原子操作");
23 ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
24
25 // 使用原子操作而不是检查然后设置
26 map.computeIfAbsent("key", k -> 1);
27
28 // 而不是
29 // if (!map.containsKey("key")) {
30 // map.put("key", 1);
31 // }
32
33 // 正确:避免过度同步
34 System.out.println("3. 避免过度同步");
35 // 使用并发容器而不是手动同步
36 Map<String, String> safeMap = new ConcurrentHashMap<>();
37 // 而不是
38 // Map<String, String> unsafeMap = Collections.synchronizedMap(new HashMap<>());
39 }
40}

9. 面试题

9.1 基础概念

Q: ConcurrentHashMap和Hashtable有什么区别?

A:

  • Hashtable:使用synchronized关键字,锁粒度大,性能较差
  • ConcurrentHashMap:使用分段锁,锁粒度小,性能更好
  • Hashtable:不允许null键值,ConcurrentHashMap允许null值
  • ConcurrentHashMap:迭代器是弱一致性的

Q: CopyOnWriteArrayList适用于什么场景?

A:

  • 读多写少的场景
  • 监听器列表等需要频繁遍历但很少修改的场景
  • 写操作会创建新副本,内存开销较大
  • 迭代器不会抛出ConcurrentModificationException

9.2 性能相关

Q: BlockingQueue的几种实现有什么区别?

A:

  • ArrayBlockingQueue:有界队列,基于数组
  • LinkedBlockingQueue:有界或无界队列,基于链表
  • PriorityBlockingQueue:无界优先级队列
  • SynchronousQueue:不存储元素的阻塞队列

Q: 如何选择合适的并发容器?

A:

  • 高并发读写:ConcurrentHashMap
  • 读多写少:CopyOnWriteArrayList
  • 生产者消费者:BlockingQueue
  • 需要排序:ConcurrentSkipListMap
  • 简单同步:Collections.synchronizedXXX()

9.3 实际应用

Q: ConcurrentHashMap的size()方法是如何实现的?

A:

  • 遍历所有段,累加每个段的元素数量
  • 由于并发修改,size()返回的是近似值
  • 如果需要精确值,可以使用mappingCount()方法
  • 在高并发环境下,size()的性能可能不如预期

Q: 如何避免并发容器的常见问题?

A:

  • 使用原子操作而不是检查然后设置
  • 避免在迭代时修改集合(除了CopyOnWriteArrayList)
  • 合理选择容器类型
  • 注意内存开销

10. 总结

Java并发容器为多线程编程提供了强大而高效的支持。

10.1 关键要点

  1. 容器特性:ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue等
  2. 性能特点:不同容器适用于不同场景
  3. 选择原则:根据读写比例、并发程度、功能需求选择
  4. 最佳实践:避免常见陷阱,合理使用

10.2 选择建议

场景推荐容器原因
高并发读写ConcurrentHashMap分段锁,性能好
读多写少CopyOnWriteArrayList写时复制,读性能好
生产者消费者BlockingQueue阻塞操作,线程安全
需要排序ConcurrentSkipListMap自动排序,并发安全
简单同步Collections.synchronizedXXX()简单易用

10.3 学习建议

  1. 理解原理:深入理解各种容器的实现原理
  2. 性能测试:对比不同容器的性能差异
  3. 场景应用:在实际项目中应用合适的容器
  4. 持续学习:关注新的并发容器技术

通过深入理解和熟练运用这些并发容器,我们能够构建出更加高效、健壮和可维护的Java并发应用程序。

参与讨论