5Java 并发编程实战

基础构建模块

并发集合和同步工具

同步集合阻塞队列并发 MapCountDownLatchSemaphore

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第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,使用分段锁实现高并发。

java
// 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,写时复制。

java
// CopyOnWriteArrayList 示例
List<String> list = new CopyOnWriteArrayList<>();
list.add("item");

// 迭代时不会抛出 ConcurrentModificationException
for (String item : list) {
    System.out.println(item);
}

特点:

  • 写操作时复制整个数组
  • 读操作不需要加锁
  • 适合读多写少的场景
  • 内存开销较大

2.2 阻塞队列

阻塞队列是支持两个附加操作的队列:

  • 当队列为空时,获取元素的线程会等待
  • 当队列满时,添加元素的线程会等待
java
// 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 允许一个或多个线程等待其他线程完成操作。

java
// 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 控制同时访问的线程数。

java
// 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 让多个线程等待彼此。

java
// 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 用于两个线程之间交换数据。

java
// 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 实现缓存

java
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 使用阻塞队列实现生产者-消费者模式

java
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 等待多个服务启动

java
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 框架。


5/15基础构建
0%