第5章:基础构建模块
一、导读
5.1 本章学习目标
本章讲解 Java 并发包中的基础构建模块,包括同步集合和同步工具。这些工具是编写并发程序的基础,理解它们对于设计高效的并发程序至关重要。
本章的核心学习目标包括:
- 掌握同步集合:ConcurrentHashMap、CopyOnWriteArrayList
- 理解阻塞队列:BlockingQueue、ArrayBlockingQueue、LinkedBlockingQueue
- 掌握并发 Map:ConcurrentHashMap、ConcurrentSkipListMap
- 理解同步工具:CountDownLatch、Semaphore、CyclicBarrier、Exchanger
5.2 为什么这一章如此重要
Java 并发包提供了丰富的基础构建模块,它们都是经过精心设计和测试的,可以直接使用。理解这些工具,对于:
- 编写高效的并发程序:使用合适的并发集合
- 实现复杂的并发模式:使用同步工具协调线程
- 避免常见的并发错误:使用线程安全的集合
二、核心概念详解
2.1 同步集合
2.1.1 ConcurrentHashMap
ConcurrentHashMap 是线程安全的 HashMap,使用分段锁实现高并发。
// ConcurrentHashMap 示例
ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);
Integer value = map.get("key");
// 原子操作
map.putIfAbsent("key", 1);
map.replace("key", 1, 2);
map.computeIfAbsent("key", k -> 1);特点:
- 使用分段锁,允许多个线程同时访问不同的段
- 读操作不需要加锁
- 写操作只锁定相关的段
- 不允许 null 键和 null 值
2.1.2 CopyOnWriteArrayList
CopyOnWriteArrayList 是线程安全的 ArrayList,写时复制。
// CopyOnWriteArrayList 示例
List<String> list = new CopyOnWriteArrayList<>();
list.add("item");
// 迭代时不会抛出 ConcurrentModificationException
for (String item : list) {
System.out.println(item);
}特点:
- 写操作时复制整个数组
- 读操作不需要加锁
- 适合读多写少的场景
- 内存开销较大
2.2 阻塞队列
阻塞队列是支持两个附加操作的队列:
- 当队列为空时,获取元素的线程会等待
- 当队列满时,添加元素的线程会等待
// ArrayBlockingQueue 示例
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
// 添加元素(会阻塞)
queue.put("item");
// 获取元素(会阻塞)
String item = queue.take();
// 尝试添加(不阻塞)
boolean added = queue.offer("item", 1, TimeUnit.SECONDS);
// 尝试获取(不阻塞)
String item = queue.poll(1, TimeUnit.SECONDS);常用阻塞队列:
- ArrayBlockingQueue:有界数组队列
- LinkedBlockingQueue:可选有界链表队列
- PriorityBlockingQueue:优先级队列
- DelayQueue:延迟队列
- SynchronousQueue:同步队列(不存储元素)
2.3 同步工具
2.3.1 CountDownLatch
CountDownLatch 允许一个或多个线程等待其他线程完成操作。
// CountDownLatch 示例
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("Task started");
// 执行任务
latch.countDown(); // 计数减1
}).start();
}
latch.await(); // 等待所有线程完成
System.out.println("All tasks completed");特点:
- 计数器只能使用一次
- 一个线程等待多个线程完成
- 适合启动多个服务
2.3.2 Semaphore
Semaphore 控制同时访问的线程数。
// Semaphore 示例
Semaphore semaphore = new Semaphore(3); // 最多 3 个线程同时访问
semaphore.acquire(); // 获取许可
try {
// 访问资源
System.out.println("Accessing resource");
Thread.sleep(1000);
} finally {
semaphore.release(); // 释放许可
}特点:
- 控制并发访问的线程数
- 适合资源池(连接池、线程池)
- 可以获取多个许可
2.3.3 CyclicBarrier
CyclicBarrier 让多个线程等待彼此。
// CyclicBarrier 示例
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("All threads arrived");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("Thread started");
try {
barrier.await(); // 等待其他线程
System.out.println("Thread continued");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}特点:
- 可以重复使用
- 多个线程等待彼此
- 适合并行计算
2.3.4 Exchanger
Exchanger 用于两个线程之间交换数据。
// Exchanger 示例
Exchanger<String> exchanger = new Exchanger<>();
new Thread(() -> {
try {
String data = "Data from thread 1";
String received = exchanger.exchange(data);
System.out.println("Thread 1 received: " + received);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
String data = "Data from thread 2";
String received = exchanger.exchange(data);
System.out.println("Thread 2 received: " + received);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();特点:
- 两个线程交换数据
- 适合生产者-消费者模式
三、重要知识点
3.1 并发集合的性能
| 集合 | 读性能 | 写性能 | 内存开销 |
|---|---|---|---|
| HashMap | 高 | 高 | 低 |
| Hashtable | 中 | 低 | 低 |
| ConcurrentHashMap | 高 | 高 | 中 |
| CopyOnWriteArrayList | 高 | 低 | 高 |
3.2 阻塞队列的选择
| 队列 | 特点 | 适用场景 |
|---|---|---|
| ArrayBlockingQueue | 有界、数组 | 固定大小的队列 |
| LinkedBlockingQueue | 可选有界、链表 | 灵活的队列大小 |
| PriorityBlockingQueue | 优先级 | 按优先级处理 |
| DelayQueue | 延迟 | 定时任务 |
| SynchronousQueue | 不存储 | 直接传递 |
3.3 同步工具的选择
| 工具 | 用途 | 可重用 |
|---|---|---|
| CountDownLatch | 等待多个线程完成 | 否 |
| CyclicBarrier | 多个线程等待彼此 | 是 |
| Semaphore | 控制并发访问数 | 是 |
| Exchanger | 两个线程交换数据 | 是 |
四、常见误区与难点
4.1 误区一:"ConcurrentHashMap 完全替代 HashMap"
ConcurrentHashMap 的性能比 HashMap 略低,只有在需要线程安全时才使用。
4.2 误区二:"阻塞队列可以无限大"
ArrayBlockingQueue 是有界的,需要指定容量。LinkedBlockingQueue 可以指定容量,默认是 Integer.MAX_VALUE。
4.3 难点:理解分段锁
ConcurrentHashMap 使用分段锁,将数据分成多个段,每个段有自己的锁。理解分段锁的原理对于理解 ConcurrentHashMap 的性能至关重要。
4.4 难点:选择合适的同步工具
不同的同步工具有不同的用途,需要根据场景选择合适的工具。
五、实践应用
5.1 使用 ConcurrentHashMap 实现缓存
public class Cache<K, V> {
private final ConcurrentMap<K, V> map = new ConcurrentHashMap<>();
public V get(K key) {
return map.get(key);
}
public void put(K key, V value) {
map.put(key, value);
}
public V computeIfAbsent(K key, Function<K, V> mappingFunction) {
return map.computeIfAbsent(key, mappingFunction);
}
}5.2 使用阻塞队列实现生产者-消费者模式
public class ProducerConsumer {
private final BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
public void producer() {
while (true) {
try {
String item = produce();
queue.put(item);
} catch (InterruptedException e) {
break;
}
}
}
public void consumer() {
while (true) {
try {
String item = queue.take();
consume(item);
} catch (InterruptedException e) {
break;
}
}
}
private String produce() {
return "item";
}
private void consume(String item) {
System.out.println("Consumed: " + item);
}
}5.3 使用 CountDownLatch 等待多个服务启动
public class ServiceStarter {
public void startServices() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
startService("Service1", latch);
startService("Service2", latch);
startService("Service3", latch);
latch.await(); // 等待所有服务启动
System.out.println("All services started");
}
private void startService(String name, CountDownLatch latch) {
new Thread(() -> {
System.out.println(name + " starting...");
// 启动服务
latch.countDown();
}).start();
}
}六、本章小结
第5章讲解了 Java 并发包中的基础构建模块:
同步集合:ConcurrentHashMap、CopyOnWriteArrayList
阻塞队列:ArrayBlockingQueue、LinkedBlockingQueue
同步工具:CountDownLatch、Semaphore、CyclicBarrier、Exchanger
这些工具都是经过精心设计和测试的,可以直接使用。理解它们的原理和适用场景,对于编写高效的并发程序至关重要。
后续章节将讲解任务执行和 Executor 框架。