第七章:多线程与并发编程
4. 线程池
在实际应用中,频繁创建和销毁线程会带来很大的系统开销。线程池通过复用线程来减少这种开销,提高系统性能。
4.1 线程池的基本概念
想象一个游泳池:与其每次想游泳时都挖一个新池子(创建新线程),不如建一个可以重复使用的游泳池(线程池)。人们可以来游泳(任务执行),游完后池子仍然存在,等待下一位游泳者。
线程池的优势:
- 减少资源消耗:重复利用已创建的线程,减少线程创建和销毁的开销
- 提高响应速度:任务到达时,无需等待线程创建就能立即执行
- 提高线程的可管理性:统一管理线程,避免无限制创建线程导致系统崩溃
4.2 Java中的线程池
Java通过Executors
工厂类提供了几种常用的线程池实现:
固定大小线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小为5的线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交10个任务给线程池执行
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("任务" + taskId + "开始执行,线程名:" + Thread.currentThread().getName());
try {
// 模拟任务执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务" + taskId + "执行完毕");
});
}
// 关闭线程池(不再接受新任务,等待所有任务完成后关闭)
executor.shutdown();
}
}
缓存线程池
public class CachedThreadPoolExample {
public static void main(String[] args) {
// 创建一个缓存线程池(按需创建线程,空闲线程会被回收)
ExecutorService executor = Executors.newCachedThreadPool();
// 提交任务
for (int i = 0; i < 20; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("任务" + taskId + "开始执行,线程名:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 模拟任务提交间隔
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
executor.shutdown();
}
}
单线程执行器
public class SingleThreadExecutorExample {
public static void main(String[] args) {
// 创建一个单线程执行器(所有任务按提交顺序执行)
ExecutorService executor = Executors.newSingleThreadExecutor();
// 提交多个任务
for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("任务" + taskId + "开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务" + taskId + "执行完毕");
});
}
executor.shutdown();
}
}
定时任务线程池
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolExample {
public static void main(String[] args) {
// 创建一个定时任务线程池
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
// 延迟3秒后执行任务
executor.schedule(() -> {
System.out.println("延迟任务执行");
}, 3, TimeUnit.SECONDS);
// 延迟1秒后,每2秒执行一次任务
executor.scheduleAtFixedRate(() -> {
System.out.println("周期任务执行,时间:" + System.currentTimeMillis()/1000);
}, 1, 2, TimeUnit.SECONDS);
// 注意:这里不调用shutdown,因为是周期性任务
}
}
4.3 自定义线程池
通过ThreadPoolExecutor
类可以创建自定义线程池,更精细地控制线程池的行为:
import java.util.concurrent.*;
public class CustomThreadPoolExample {
public static void main(String[] args) {
// 创建自定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60, TimeUnit.SECONDS, // 空闲线程存活时间
new ArrayBlockingQueue<>(10), // 工作队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 提交任务
for (int i = 0; i < 15; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("任务" + taskId + "开始执行,线程名:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown();
}
}
参数说明:
- 核心线程数:线程池中保持活跃的线程数量
- 最大线程数:线程池中允许的最大线程数
- 空闲线程存活时间:超过核心线程数的空闲线程在被回收前的等待时间
- 工作队列:存储等待执行的任务的队列
- 线程工厂:创建新线程的工厂
- 拒绝策略:当线程池和队列都满时的处理策略
4.4 线程池的生命周期
线程池有以下几种状态:
- 运行(RUNNING):接受新任务并处理队列中的任务
- 关闭(SHUTDOWN):不接受新任务,但处理队列中的任务
- 停止(STOP):不接受新任务,不处理队列中的任务,中断正在执行的任务
- 整理(TIDYING):所有任务已终止,工作线程数为0
- 终止(TERMINATED):线程池已完全终止
// 关闭线程池(等待任务完成)
executor.shutdown();
// 立即关闭线程池(尝试停止所有正在执行的任务)
List<Runnable> unfinishedTasks = executor.shutdownNow();
// 等待线程池终止(最多等待5秒)
boolean terminated = executor.awaitTermination(5, TimeUnit.SECONDS);
4.5 线程池的实际应用
网页爬虫
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class WebCrawlerExample {
public static void main(String[] args) {
List<String> urlsToCrawl = new ArrayList<>();
// 添加要爬取的URL
urlsToCrawl.add("https://example.com/page1");
urlsToCrawl.add("https://example.com/page2");
// ... 添加更多URL
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交爬取任务
for (String url : urlsToCrawl) {
executor.execute(() -> crawlPage(url));
}
// 关闭线程池
executor.shutdown();
try {
// 等待所有任务完成,最多等待1小时
executor.awaitTermination(1, TimeUnit.HOURS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有页面爬取完成");
}
private static void crawlPage(String url) {
System.out.println("爬取页面:" + url + ",线程:" + Thread.currentThread().getName());
// 模拟页面爬取过程
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("页面爬取完成:" + url);
}
}
图片处理
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ImageProcessingExample {
public static void main(String[] args) {
String[] imageFiles = {"image1.jpg", "image2.jpg", "image3.jpg", "image4.jpg", "image5.jpg"};
// 创建与CPU核心数相同的线程池
int processors = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(processors);
// 提交图片处理任务
for (String imageFile : imageFiles) {
executor.execute(() -> processImage(imageFile));
}
executor.shutdown();
}
private static void processImage(String imageFile) {
System.out.println("处理图片:" + imageFile + ",线程:" + Thread.currentThread().getName());
// 模拟图片处理
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("图片处理完成:" + imageFile);
}
}
5. 并发集合
Java提供了多种线程安全的集合类,用于在多线程环境下安全地操作数据。
5.1 并发集合概述
想象一个繁忙的餐厅:多个服务员(线程)同时查看和更新点菜单(集合)。如果使用普通的点菜单(非线程安全集合),可能会出现混乱;而使用专门设计的电子点菜系统(并发集合)则可以确保点单过程的正确性。
常见的并发集合类:
非线程安全集合 | 对应的并发集合 | 特点 |
---|---|---|
ArrayList | CopyOnWriteArrayList | 适用于读多写少的场景 |
HashSet | CopyOnWriteArraySet | 基于CopyOnWriteArrayList实现 |
HashMap | ConcurrentHashMap | 分段锁实现,高并发性能好 |
Queue | ConcurrentLinkedQueue | 非阻塞队列 |
BlockingQueue | ArrayBlockingQueue等 | 支持阻塞操作的队列 |
5.2 ConcurrentHashMap
ConcurrentHashMap
是HashMap
的线程安全版本,采用分段锁技术,支持高并发访问。
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ConcurrentHashMapExample {
public static void main(String[] args) throws InterruptedException {
// 对比HashMap和ConcurrentHashMap
final Map<String, Integer> hashMap = new HashMap<>();
final Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
// 测试HashMap(可能会抛出ConcurrentModificationException)
try {
testMap(executor, hashMap, "HashMap");
} catch (Exception e) {
System.out.println("HashMap测试失败:" + e.getMessage());
}
// 测试ConcurrentHashMap
testMap(executor, concurrentMap, "ConcurrentHashMap");
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
// 输出结果
System.out.println("HashMap大小:" + hashMap.size());
System.out.println("ConcurrentHashMap大小:" + concurrentMap.size());
}
private static void testMap(ExecutorService executor, final Map<String, Integer> map, String mapName) {
// 10个线程同时写入
for (int i = 0; i < 10; i++) {
final int threadNum = i;
executor.execute(() -> {
for (int j = 0; j < 100; j++) {
map.put(mapName + "-" + threadNum + "-" + j, j);
}
});
}
}
}
5.3 CopyOnWriteArrayList
CopyOnWriteArrayList
是ArrayList
的线程安全版本,适用于读多写少的场景。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListExample {
public static void main(String[] args) {
// 创建普通ArrayList和CopyOnWriteArrayList
List<String> normalList = new ArrayList<>();
List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
// 添加元素
for (int i = 0; i < 10; i++) {
normalList.add("Item " + i);
copyOnWriteList.add("Item " + i);
}
// 使用迭代器遍历并尝试修改列表
System.out.println("=== ArrayList测试 ===");
try {
Iterator<String> iterator = normalList.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
if (item.equals("Item 5")) {
// 这会抛出ConcurrentModificationException
normalList.remove(item);
}
}
} catch (Exception e) {
System.out.println("发生异常:" + e.getMessage());
}
System.out.println("\n=== CopyOnWriteArrayList测试 ===");
Iterator<String> iterator = copyOnWriteList.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
if (item.equals("Item 5")) {
// 这不会抛出异常,但迭代器不会看到修改后的列表
copyOnWriteList.remove(item);
System.out.println("已移除Item 5,但迭代器仍然可以看到它");
}
}
System.out.println("\nCopyOnWriteArrayList当前内容:");
for (String item : copyOnWriteList) {
System.out.println(item);
}
}
}
5.4 阻塞队列
阻塞队列是支持两个附加操作的队列:
- 当队列为空时,获取元素的线程会等待队列变为非空
- 当队列满时,存储元素的线程会等待队列可用
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueExample {
public static void main(String[] args) {
// 创建容量为3的阻塞队列
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
// 生产者线程
Thread producer = new Thread(() -> {
try {
System.out.println("生产者开始生产数据");
for (int i = 0; i < 6; i++) {
String data = "Data-" + i;
System.out.println("生产数据:" + data);
queue.put(data); // 如果队列满,会阻塞
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 消费者线程
Thread consumer = new Thread(() -> {
try {
System.out.println("消费者开始消费数据");
while (true) {
// 模拟消费者处理速度较慢
Thread.sleep(2000);
String data = queue.take(); // 如果队列空,会阻塞
System.out.println("消费数据:" + data);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
5.5 并发集合的选择
选择合适的并发集合需要考虑以下因素:
- 读写比例:读多写少选择
CopyOnWriteArrayList
,读写均衡选择ConcurrentHashMap
- 是否需要阻塞操作:需要阻塞选择
BlockingQueue
,不需要选择ConcurrentLinkedQueue
- 性能要求:高性能要求选择非阻塞集合,如
ConcurrentHashMap
- 数据一致性要求:强一致性要求可能需要额外的同步措施
6. 原子类
原子类是Java提供的一组支持原子操作的类,可以在不使用锁的情况下实现线程安全。
6.1 原子类基本概念
想象一个计数器:多个人同时操作一个机械计数器,每人增加一次。如果大家同时按下按钮,计数器可能只增加一次而不是多次。原子类就像一个特殊设计的计数器,确保每次按下都会被准确记录,不会丢失。
原子操作是指不会被线程调度机制中断的操作,这种操作一旦开始,就一定会执行完毕。
6.2 常用原子类
Java提供了多种原子类:
-
基本类型原子类:
AtomicInteger
:整型的原子类AtomicLong
:长整型的原子类AtomicBoolean
:布尔型的原子类
-
数组原子类:
AtomicIntegerArray
:整型数组原子类AtomicLongArray
:长整型数组原子类AtomicReferenceArray
:引用类型数组原子类
-
引用原子类:
AtomicReference
:引用类型原子类AtomicStampedReference
:带有版本号的引用类型原子类AtomicMarkableReference
:带有标记的引用类型原子类
-
字段更新器:
AtomicIntegerFieldUpdater
:整型字段的原子更新器AtomicLongFieldUpdater
:长整型字段的原子更新器AtomicReferenceFieldUpdater
:引用类型字段的原子更新器
6.3 AtomicInteger示例
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class AtomicIntegerExample {
// 普通整数计数器(非线程安全)
private static int normalCounter = 0;
// 原子整数计数器(线程安全)
private static AtomicInteger atomicCounter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(10);
// 10个线程同时执行
for (int i = 0; i < 10; i++) {
executor.execute(() -> {
for (int j = 0; j < 1000; j++) {
// 普通计数器递增
normalCounter++;
// 原子计数器递增
atomicCounter.incrementAndGet();
}
});
}
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("普通计数器最终值:" + normalCounter);
System.out.println("原子计数器最终值:" + atomicCounter.get());
}
}
6.4 CAS操作
原子类的核心是**比较并交换(Compare And Swap, CAS)**操作,这是一种无锁算法,用于在多线程环境下安全地修改共享变量。
CAS操作包含三个操作数:
- 内存位置(V):要更新的变量
- 预期原值(A):更新前的预期值
- 新值(B):要设置的新值
CAS的工作原理:只有当V的当前值等于A时,才会将V的值更新为B,否则不会执行更新操作。整个比较和更新操作是原子的。
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
public static void main(String[] args) {
AtomicInteger atomicInt = new AtomicInteger(100);
// 使用CAS操作更新值
boolean success = atomicInt.compareAndSet(100, 200);
System.out.println("CAS操作结果:" + success + ",当前值:" + atomicInt.get());
// 再次尝试CAS操作(预期值不匹配,将失败)
success = atomicInt.compareAndSet(100, 300);
System.out.println("CAS操作结果:" + success + ",当前值:" + atomicInt.get());
// 获取当前值并增加
int oldValue = atomicInt.getAndAdd(50);
System.out.println("增加前的值:" + oldValue + ",增加后的值:" + atomicInt.get());
// 增加并获取新值
int newValue = atomicInt.addAndGet(50);
System.out.println("增加后的新值:" + newValue);
}
}
6.5 原子引用
AtomicReference
可以让你原子性地更新引用类型:
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceExample {
static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
public static void main(String[] args) {
// 创建原子引用
AtomicReference<User> userRef = new AtomicReference<>(new User("Tom", 20));
// 获取当前引用
User oldUser = userRef.get();
System.out.println("原始用户:" + oldUser);
// 创建新用户
User newUser = new User("Jerry", 25);
// 原子性地更新引用
boolean success = userRef.compareAndSet(oldUser, newUser);
System.out.println("更新结果:" + success + ",当前用户:" + userRef.get());
}
}
6.6 ABA问题与解决方案
ABA问题是CAS操作的一个常见问题:如果一个值原来是A,变成了B,又变回了A,使用CAS检查时会认为它没有变化,但实际上已经发生了变化。
AtomicStampedReference
通过引入版本号解决ABA问题:
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABAExample {
public static void main(String[] args) throws InterruptedException {
// 初始值为100,初始版本为1
AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(100, 1);
// 获取当前版本号
int initialStamp = atomicStampedRef.getStamp();
Integer initialValue = atomicStampedRef.getReference();
System.out.println("初始值:" + initialValue + ",初始版本号:" + initialStamp);
// 线程1:将100 -> 101 -> 100,模拟ABA问题
Thread thread1 = new Thread(() -> {
// 先更新为101
atomicStampedRef.compareAndSet(100, 101, initialStamp, initialStamp + 1);
int stamp = atomicStampedRef.getStamp();
// 再更新回100,但版本号已经改变
atomicStampedRef.compareAndSet(101, 100, stamp, stamp + 1);
System.out.println("线程1完成ABA操作,当前值:" + atomicStampedRef.getReference() +
",当前版本号:" + atomicStampedRef.getStamp());
});
// 线程2:期望更新100 -> 200,但会检查版本号
Thread thread2 = new Thread(() -> {
try {
// 让线程1先执行完ABA操作
Thread.sleep(1000);
// 尝试更新,使用初始版本号
boolean success = atomicStampedRef.compareAndSet(100, 200, initialStamp, initialStamp + 1);
System.out.println("线程2更新结果:" + success + ",当前值:" + atomicStampedRef.getReference() +
",当前版本号:" + atomicStampedRef.getStamp());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
7. 并发工具类
Java提供了一系列并发工具类,用于解决多线程编程中的常见问题,如线程协作、同步控制等。
7.1 CountDownLatch
CountDownLatch
是一个同步辅助类,允许一个或多个线程等待,直到其他线程完成一组操作。
想象一场赛跑:裁判需要等待所有运动员(线程)都到达起跑线后才能发令开始。CountDownLatch
就像裁判手中的计数器,每个运动员到位后计数减一,当所有人都准备好(计数为零)时,比赛开始。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
// 创建一个初始计数为5的CountDownLatch
CountDownLatch latch = new CountDownLatch(5);
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
System.out.println("主线程开始等待所有工作线程完成...");
// 提交5个任务
for (int i = 0; i < 5; i++) {
final int taskId = i + 1;
executor.execute(() -> {
try {
// 模拟任务执行
System.out.println("任务" + taskId + "开始执行");
Thread.sleep((long) (Math.random() * 3000));
System.out.println("任务" + taskId + "执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 任务完成,计数减一
latch.countDown();
}
});
}
// 等待所有任务完成
latch.await();
System.out.println("所有任务已完成,主线程继续执行");
executor.shutdown();
}
}
7.2 CyclicBarrier
CyclicBarrier
是一个同步辅助类,允许一组线程互相等待,直到所有线程都到达一个公共屏障点。
想象一次团队旅行:大家约定在某个地点集合后再一起出发。CyclicBarrier
就像这个集合点,每个人(线程)到达后就等待,直到所有人都到齐,然后一起前进。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
// 创建一个CyclicBarrier,等待4个线程,并在所有线程到达屏障时执行一个任务
CyclicBarrier barrier = new CyclicBarrier(4, () -> {
System.out.println("所有线程已到达屏障点,执行屏障操作");
});
// 创建4个线程
for (int i = 0; i < 4; i++) {
final int threadId = i + 1;
new Thread(() -> {
try {
System.out.println("线程" + threadId + "开始执行");
Thread.sleep((long) (Math.random() * 3000));
System.out.println("线程" + threadId + "到达屏障点,等待其他线程");
// 等待其他线程到达屏障点
barrier.await();
System.out.println("线程" + threadId + "继续执行");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
7.3 Semaphore
Semaphore
是一个计数信号量,用于控制同时访问特定资源的线程数量。
想象一个有限停车场:只有特定数量的停车位,车辆(线程)需要获取许可(信号量)才能进入,离开时释放许可给其他车辆使用。
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
// 创建一个只有3个许可的信号量(模拟3个停车位)
Semaphore semaphore = new Semaphore(3);
// 创建10辆车(10个线程)
for (int i = 0; i < 10; i++) {
final int carId = i + 1;
new Thread(() -> {
try {
System.out.println("汽车" + carId + "等待进入停车场");
// 获取许可
semaphore.acquire();
System.out.println("汽车" + carId + "进入停车场,当前可用停车位:" +
(3 - semaphore.availablePermits()));
// 模拟停车时间
Thread.sleep((long) (Math.random() * 10000));
// 释放许可
semaphore.release();
System.out.println("汽车" + carId + "离开停车场,当前可用停车位:" +
(3 - semaphore.availablePermits()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
7.4 Exchanger
Exchanger
是一个同步点,允许两个线程在这个点交换数据。
想象两个朋友交换礼物:两人必须同时到达约定地点才能完成交换。Exchanger
就像这个约定地点,两个线程都到达后才能交换数据并继续执行。
import java.util.concurrent.Exchanger;
public class ExchangerExample {
public static void main(String[] args) {
// 创建Exchanger对象
Exchanger<String> exchanger = new Exchanger<>();
// 线程A
new Thread(() -> {
try {
String dataA = "来自线程A的数据";
System.out.println("线程A准备交换数据:" + dataA);
// 等待与线程B交换数据
String dataFromB = exchanger.exchange(dataA);
System.out.println("线程A收到数据:" + dataFromB);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 线程B
new Thread(() -> {
try {
// 模拟线程B晚一点到达交换点
Thread.sleep(3000);
String dataB = "来自线程B的数据";
System.out.println("线程B准备交换数据:" + dataB);
// 等待与线程A交换数据
String dataFromA = exchanger.exchange(dataB);
System.out.println("线程B收到数据:" + dataFromA);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
7.5 Phaser
Phaser
是一个更灵活的同步屏障,允许执行多阶段任务,每个阶段结束时同步所有参与者。
想象一场多阶段比赛:所有选手必须完成当前阶段才能一起进入下一阶段。Phaser
就像赛事组织者,确保所有选手同步推进。
import java.util.concurrent.Phaser;
public class PhaserExample {
public static void main(String[] args) {
// 创建Phaser,初始参与者数量为4
Phaser phaser = new Phaser(4);
// 创建4个线程
for (int i = 0; i < 4; i++) {
final int threadId = i + 1;
new Thread(() -> {
// 第一阶段
System.out.println("线程" + threadId + "开始执行第一阶段");
sleepRandomTime();
System.out.println("线程" + threadId + "完成第一阶段,等待其他线程");
phaser.arriveAndAwaitAdvance(); // 等待所有线程完成第一阶段
// 第二阶段
System.out.println("线程" + threadId + "开始执行第二阶段");
sleepRandomTime();
System.out.println("线程" + threadId + "完成第二阶段,等待其他线程");
phaser.arriveAndAwaitAdvance(); // 等待所有线程完成第二阶段
// 第三阶段
System.out.println("线程" + threadId + "开始执行第三阶段");
sleepRandomTime();
System.out.println("线程" + threadId + "完成第三阶段");
phaser.arriveAndDeregister(); // 完成并取消注册
}).start();
}
}
private static void sleepRandomTime() {
try {
Thread.sleep((long) (Math.random() * 2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
7.6 CompletableFuture
CompletableFuture
是Java 8引入的一个类,提供了强大的异步编程能力,支持组合、链式调用和异常处理。
想象一个复杂的任务流程:你需要先获取数据,然后处理数据,最后展示结果。CompletableFuture
允许你以非阻塞的方式定义这些步骤,并处理它们之间的依赖关系。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class CompletableFutureExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建一个异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
System.out.println("异步任务开始执行,线程:" + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(2);
return "任务结果";
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
});
// 添加回调函数
future.thenAccept(result -> {
System.out.println("处理结果:" + result + ",线程:" + Thread.currentThread().getName());
});
System.out.println("主线程继续执行其他操作");
// 等待异步任务完成
future.get();
// 组合多个CompletableFuture
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
return "结果1";
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
return "结果2";
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
});
// 组合两个异步任务的结果
CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> {
return result1 + " + " + result2;
});
System.out.println("组合结果:" + combinedFuture.get());
// 异常处理
CompletableFuture<String> failedFuture = CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("故意抛出异常");
return "不会返回的结果";
}).exceptionally(ex -> {
System.out.println("捕获到异常:" + ex.getMessage());
return "默认结果";
});
System.out.println("异常处理后的结果:" + failedFuture.get());
}
}
7.7 并发工具类的选择
选择合适的并发工具类需要考虑以下因素:
-
线程协作模式:
- 一个线程等待多个线程完成:
CountDownLatch
- 多个线程相互等待:
CyclicBarrier
或Phaser
- 控制并发访问数量:
Semaphore
- 两个线程交换数据:
Exchanger
- 一个线程等待多个线程完成:
-
任务类型:
- 异步任务处理:
CompletableFuture
- 多阶段任务:
Phaser
- 异步任务处理:
-
重用性:
- 需要重复使用:
CyclicBarrier
、Semaphore
、Phaser
- 一次性使用:
CountDownLatch
- 需要重复使用:
总结
本章补充内容介绍了Java并发编程中的重要组件:线程池、并发集合、原子类和并发工具类。这些工具和类库为开发高效、安全的多线程应用提供了强大支持。
- 线程池:通过复用线程减少创建和销毁线程的开销,提高系统性能
- 并发集合:提供线程安全的集合类,避免在多线程环境下使用同步代码块
- 原子类:提供原子操作,无需使用锁就能保证线程安全
- 并发工具类:提供多种线程协作工具,简化复杂的多线程交互
掌握这些工具和技术,将使你能够开发出高效、稳定的并发应用程序。