引言
在现代软件开发中,并发编程已经成为构建高性能应用的核心技能。Java作为企业级开发的主流语言,提供了丰富而强大的并发工具。本文将带你从最基础的synchronized关键字开始,一路探索到现代异步编程的利器CompletableFuture,帮助你建立完整的Java并发编程知识体系。
一、并发编程的基石:synchronized
1.1 为什么需要synchronized
在多线程环境中,当多个线程同时访问共享资源时,可能会导致数据不一致的问题。这就是经典的"竞态条件"(Race Condition)。
public class Counter {
private int count = 0;
public void increment() {
count++; // 非原子操作,存在线程安全问题
}
}
上面的代码看似简单,但count++实际上包含三个步骤:读取、加1、写回。在多线程环境下,这三个步骤可能会被其他线程打断,导致计数错误。
1.2 synchronized的使用方式
synchronized关键字提供了三种使用方式:
方法级别同步:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
代码块同步:
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized(lock) {
count++;
}
}
}
静态方法同步:
public class Counter {
private static int count = 0;
public static synchronized void increment() {
count++;
}
}
1.3 synchronized的原理
synchronized的底层是通过监视器锁(Monitor)实现的。每个Java对象都可以作为锁,当线程进入synchronized代码块时,它会尝试获取对象的监视器锁。如果锁已被其他线程持有,当前线程会进入阻塞状态。
JVM对synchronized进行了多项优化,包括:
- 偏向锁:在只有一个线程访问时,减少锁开销
- 轻量级锁:使用CAS操作避免互斥量的使用
- 锁粗化:合并相邻的同步块
- 锁消除:JIT编译时移除不必要的锁
二、轻量级同步:volatile关键字
2.1 volatile的作用
volatile关键字解决两个问题:
- 可见性:保证变量修改对所有线程立即可见
- 有序性:防止指令重排序
public class VolatileExample {
private volatile boolean flag = false;
public void writer() {
flag = true; // 写操作
}
public void reader() {
if (flag) { // 读操作,能立即看到flag的变化
// 执行某些操作
}
}
}
2.2 volatile vs synchronized
volatile不能替代synchronized,因为它不保证原子性:
public class VolatileCounter {
private volatile int count = 0;
public void increment() {
count++; // 仍然不是线程安全的!
}
}
volatile适用于以下场景:
- 状态标志
- 一写多读的场景
- 双重检查锁定(DCL)模式
public class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
三、显式锁:Lock接口
3.1 Lock接口的优势
Java 5引入了Lock接口,提供了比synchronized更灵活的锁机制:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockCounter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // 必须在finally中释放锁
}
}
}
Lock接口的优势包括:
- 可中断的锁获取:支持
lockInterruptibly() - 尝试非阻塞获取锁:支持
tryLock() - 超时获取锁:支持
tryLock(long time, TimeUnit unit) - 公平锁:可以选择公平或非公平策略
3.2 ReentrantLock的高级用法
public class AdvancedLockExample {
private final ReentrantLock lock = new ReentrantLock(true); // 公平锁
public void timeoutLock() {
try {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 执行临界区代码
} finally {
lock.unlock();
}
} else {
// 获取锁超时的处理
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
3.3 ReadWriteLock读写锁
对于读多写少的场景,ReadWriteLock可以显著提升性能:
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Cache {
private final Map<String, Object> cache = new HashMap<>();
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
public Object get(String key) {
rwLock.readLock().lock();
try {
return cache.get(key);
} finally {
rwLock.readLock().unlock();
}
}
public void put(String key, Object value) {
rwLock.writeLock().lock();
try {
cache.put(key, value);
} finally {
rwLock.writeLock().unlock();
}
}
}
四、线程池:高效的线程管理
4.1 为什么使用线程池
频繁创建和销毁线程会带来性能开销。线程池通过复用线程来解决这个问题:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
private final ExecutorService executor = Executors.newFixedThreadPool(10);
public void executeTask() {
executor.submit(() -> {
// 执行任务
System.out.println("任务执行中: " + Thread.currentThread().getName());
});
}
public void shutdown() {
executor.shutdown();
}
}
4.2 线程池的核心参数
使用ThreadPoolExecutor可以精确控制线程池行为:
import java.util.concurrent.*;
public class CustomThreadPool {
private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60L, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(100), // 工作队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
}
线程池的工作流程:
- 当前线程数 < 核心线程数:创建新线程执行任务
- 当前线程数 >= 核心线程数:任务加入队列
- 队列已满且线程数 < 最大线程数:创建新线程
- 队列已满且线程数 = 最大线程数:执行拒绝策略
4.3 常用的拒绝策略
// 1. AbortPolicy:抛出异常(默认)
// 2. CallerRunsPolicy:调用者线程执行
// 3. DiscardPolicy:静默丢弃
// 4. DiscardOldestPolicy:丢弃队列中最旧的任务
// 自定义拒绝策略
public class CustomRejectedHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 记录日志或执行其他操作
System.err.println("任务被拒绝: " + r.toString());
}
}
五、Future:获取异步结果
5.1 Callable与Future
Callable接口允许任务返回结果:
import java.util.concurrent.*;
public class FutureExample {
private final ExecutorService executor = Executors.newFixedThreadPool(5);
public void calculateAsync() throws Exception {
Future<Integer> future = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(1000);
return 42;
}
});
// 执行其他操作...
// 获取结果(阻塞直到任务完成)
Integer result = future.get();
System.out.println("结果: " + result);
}
}
5.2 Future的局限性
传统的Future存在以下问题:
- 阻塞获取结果:get()方法会阻塞
- 无法链式调用:不支持任务组合
- 无法手动完成:不能主动设置结果
- 缺少异常处理:异常处理不够优雅
这些问题促使Java 8引入了CompletableFuture。
六、CompletableFuture:现代异步编程
6.1 创建CompletableFuture
CompletableFuture提供了多种创建方式:
import java.util.concurrent.CompletableFuture;
public class CompletableFutureBasics {
// 方式1: 使用runAsync执行无返回值任务
public CompletableFuture<Void> runAsync() {
return CompletableFuture.runAsync(() -> {
System.out.println("异步执行任务");
});
}
// 方式2: 使用supplyAsync执行有返回值任务
public CompletableFuture<String> supplyAsync() {
return CompletableFuture.supplyAsync(() -> {
return "异步计算结果";
});
}
// 方式3: 手动创建并完成
public CompletableFuture<String> manualComplete() {
CompletableFuture<String> future = new CompletableFuture<>();
// 在某个时刻完成
future.complete("手动设置的结果");
return future;
}
}
6.2 链式操作与转换
CompletableFuture的强大之处在于可以链式组合多个异步操作:
public class CompletableFutureChaining {
public void chainExample() {
CompletableFuture.supplyAsync(() -> {
return "Hello";
})
.thenApply(s -> s + " World") // 转换结果
.thenApply(String::toUpperCase) // 继续转换
.thenAccept(System.out::println) // 消费结果
.exceptionally(ex -> { // 异常处理
System.err.println("出错了: " + ex.getMessage());
return null;
});
}
}
6.3 组合多个CompletableFuture
public class CompletableFutureCombination {
// 组合两个独立的异步任务
public void combineExample() {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combined = future1.thenCombine(future2,
(s1, s2) -> s1 + " " + s2);
combined.thenAccept(System.out::println); // 输出: Hello World
}
// 等待所有任务完成
public void allOfExample() {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task2");
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Task3");
CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2, future3);
allFutures.thenRun(() -> {
System.out.println("所有任务完成!");
});
}
// 任意一个完成即可
public void anyOfExample() {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
sleep(100);
return "Fast";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
sleep(200);
return "Slow";
});
CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2);
anyFuture.thenAccept(result ->
System.out.println("最快完成的结果: " + result));
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
6.4 异常处理
CompletableFuture提供了优雅的异常处理机制:
public class CompletableFutureExceptionHandling {
public void exceptionHandling() {
CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) {
throw new RuntimeException("随机异常");
}
return "成功";
})
.exceptionally(ex -> {
// 处理异常并返回默认值
System.err.println("捕获异常: " + ex.getMessage());
return "默认值";
})
.thenAccept(result -> System.out.println("最终结果: " + result));
}
public void handleExample() {
CompletableFuture.supplyAsync(() -> {
return 10 / 0; // 会抛出异常
})
.handle((result, ex) -> {
if (ex != null) {
System.err.println("发生异常: " + ex.getMessage());
return 0; // 返回默认值
}
return result;
})
.thenAccept(result -> System.out.println("结果: " + result));
}
public void whenCompleteExample() {
CompletableFuture.supplyAsync(() -> "Hello")
.whenComplete((result, ex) -> {
if (ex != null) {
System.err.println("异常: " + ex.getMessage());
} else {
System.out.println("成功: " + result);
}
});
}
}
6.5 实战案例:并行数据处理
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class ParallelDataProcessing {
public CompletableFuture<List<String>> processData(List<Integer> ids) {
List<CompletableFuture<String>> futures = ids.stream()
.map(id -> CompletableFuture.supplyAsync(() -> fetchData(id)))
.map(future -> future.thenApply(this::processData))
.map(future -> future.thenApply(this::enrichData))
.collect(Collectors.toList());
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()));
}
private String fetchData(Integer id) {
// 模拟从数据库或API获取数据
sleep(100);
return "Data-" + id;
}
private String processData(String data) {
// 模拟数据处理
return data.toUpperCase();
}
private String enrichData(String data) {
// 模拟数据丰富化
return data + "-ENRICHED";
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
6.6 自定义线程池
默认情况下,CompletableFuture使用ForkJoinPool.commonPool(),但你可以指定自定义线程池:
import java.util.concurrent.*;
public class CustomExecutorExample {
private final ExecutorService executor = Executors.newFixedThreadPool(10);
public void useCustomExecutor() {
CompletableFuture.supplyAsync(() -> {
return "使用自定义线程池";
}, executor) // 指定线程池
.thenApplyAsync(s -> s.toUpperCase(), executor)
.thenAccept(System.out::println);
}
public void shutdown() {
executor.shutdown();
}
}
七、最佳实践与性能优化
7.1 选择合适的并发工具
| 场景 | 推荐工具 |
|---|---|
| 简单的同步控制 | synchronized |
| 需要更灵活的锁控制 | ReentrantLock |
| 读多写少 | ReadWriteLock |
| 状态标志 | volatile |
| 批量任务执行 | 线程池 |
| 异步任务组合 | CompletableFuture |
7.2 避免常见陷阱
// 陷阱1: 忘记释放锁
Lock lock = new ReentrantLock();
lock.lock();
// 如果这里抛出异常,锁永远不会被释放!
doSomething();
lock.unlock();
// 正确做法
lock.lock();
try {
doSomething();
} finally {
lock.unlock(); // 确保锁被释放
}
// 陷阱2: 线程池忘记关闭
ExecutorService executor = Executors.newFixedThreadPool(10);
// 使用线程池...
// 忘记调用shutdown(),线程池会阻止JVM退出
// 正确做法
try {
// 使用线程池
} finally {
executor.shutdown();
}
// 陷阱3: CompletableFuture中的阻塞操作
CompletableFuture.supplyAsync(() -> {
// 不要在这里使用Thread.sleep()或其他阻塞操作
// 会占用宝贵的ForkJoinPool线程
return someBlockingOperation(); // 错误!
});
7.3 性能调优建议
-
合理设置线程池大小
- CPU密集型任务:线程数 ≈ CPU核心数 + 1
- IO密集型任务:线程数 ≈ CPU核心数 × 2
-
减少锁的粒度
// 不好:锁的范围太大
public synchronized void process() {
doSomething1();
doSomething2();
criticalSection();
}
// 更好:只锁必要的部分
public void process() {
doSomething1();
doSomething2();
synchronized(this) {
criticalSection();
}
}
- 使用并发集合
// 不要用synchronized包装普通集合
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
// 使用并发集合性能更好
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
八、总结
Java并发编程从synchronized到CompletableFuture的演进,体现了从简单粗暴的锁机制到优雅的异步编程范式的转变。每种工具都有其适用场景:
- synchronized:简单场景的首选,JVM优化良好
- Lock接口:需要更多控制时使用
- volatile:轻量级的可见性保证
- 线程池:高效管理线程资源
- Future:获取异步任务结果
- CompletableFuture:构建复杂的异步工作流
掌握这些工具,理解它们背后的原理,才能在实际项目中游刃有余地处理并发问题,构建出高性能、高可靠的应用系统。
1506

被折叠的 条评论
为什么被折叠?



