Java并发编程最佳实践
并发编程是Java中最具挑战性的领域之一,正确地实现并发不仅能够提高应用程序的性能,还能充分利用现代多核处理器的优势。本文将详细介绍Java并发编程的最佳实践,帮助开发者编写高效、安全的并发代码。
1. 线程安全策略
在并发编程中,确保线程安全是首要任务。以下是几种常用的线程安全策略:
1.1 不可变对象(Immutable Objects)
不可变对象是最简单的线程安全策略之一,因为它们一旦创建就不能被修改。
优点:
- 无需同步即可安全地在多线程间共享
- 简化并发编程
- 防止并发修改错误
实现方式:
- 所有字段都是
final - 对象创建后状态不可修改
- 确保
this引用不会逸出构造过程
public final class ImmutablePerson {
private final String name;
private final int age;
public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
1.2 线程封闭(Thread Confinement)
线程封闭是确保数据只能被单个线程访问的一种技术。
常见实现:
-
局部变量(
Local Variables)- 天然线程安全,因为它们存储在线程的栈中
- 其他线程无法访问
-
ThreadLocal类- 为每个线程提供独立的变量副本
- 适用于需要线程内共享但线程间隔离的变量
// ThreadLocal的使用示例
private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String formatDate(Date date) {
return dateFormatThreadLocal.get().format(date);
}
1.3 同步容器类(Synchronized Collections)
通过同步机制包装集合类,使其在多线程环境下安全。
// 同步List示例
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
// 同步Map示例
Map<String, Integer> synchronizedMap = Collections.synchronizedMap(new HashMap<>());
1.4 并发容器类(Concurrent Collections)
java.util.concurrent包中提供的高性能并发容器,比传统同步容器性能更好。
// 常用并发容器示例
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
BlockingQueue<Task> queue = new LinkedBlockingQueue<>();
1.5 显式锁(Explicit Locks)和同步(Synchronization)
使用synchronized关键字或java.util.concurrent.locks包中的锁。
// 使用synchronized关键字
public synchronized void increment() {
count++;
}
// 使用显式锁
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // 确保锁总是被释放
}
}
2. 死锁问题的识别与预防
死锁是指两个或更多线程互相等待对方持有的锁,导致程序无法继续执行的情况。
2.1 死锁的四个必要条件
- 互斥条件:资源不能被共享,一次只能被一个线程使用
- 请求与保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求
- 不剥夺条件:线程获得的资源在未使用完之前,不能被其他线程强行剥夺
- 循环等待条件:若干线程之间形成头尾相接的循环等待资源关系

2.2 死锁示例
public class DeadlockExample {
private final Object resource1 = new Object();
private final Object resource2 = new Object();
public void method1() {
synchronized(resource1) {
System.out.println("Thread 1: locked resource 1");
// 模拟工作
try { Thread.sleep(100); } catch (Exception e) {}
synchronized(resource2) {
System.out.println("Thread 1: locked resource 2");
}
}
}
public void method2() {
synchronized(resource2) {
System.out.println("Thread 2: locked resource 2");
// 模拟工作
try { Thread.sleep(100); } catch (Exception e) {}
synchronized(resource1) {
System.out.println("Thread 2: locked resource 1");
}
}
}
}
2.3 死锁预防策略
- 固定加锁顺序
- 始终按照相同的顺序获取锁,避免循环等待
// 改进后的代码,防止死锁
public void method1() {
synchronized(resource1) { // 总是先锁resource1
synchronized(resource2) {
// 执行操作
}
}
}
public void method2() {
synchronized(resource1) { // 总是先锁resource1
synchronized(resource2) {
// 执行操作
}
}
}
- 使用
tryLock()方法- 尝试获取锁,如果不可用,不要等待
private void transferMoney(Account fromAccount, Account toAccount, double amount) {
while (true) {
if (fromAccount.lock.tryLock()) {
try {
if (toAccount.lock.tryLock()) {
try {
// 转账操作
return;
} finally {
toAccount.lock.unlock();
}
}
} finally {
fromAccount.lock.unlock();
}
}
// 重试前短暂休眠,避免CPU密集型循环
Thread.sleep(1);
}
}
-
设置锁超时
- 使用
tryLock(timeout, unit)方法,设置获取锁的超时时间
- 使用
-
死锁检测
- 使用工具检测应用程序中的死锁
- 如JDK的
jstack工具可以检测和打印死锁信息
3. 性能与可伸缩性
性能指程序完成任务的速度,而可伸缩性则是指程序处理更多工作负载的能力。在并发编程中,两者都至关重要。
3.1 优化锁的使用
- 减少锁的粒度
- 只锁定真正需要同步的代码块,而不是整个方法
// 不好的做法:整个方法都被锁定
public synchronized void findAndUpdateUser(String userId, Consumer<User> update) {
User user = findUserById(userId); // 耗时的操作,但不需要同步
update.accept(user); // 更新用户,需要同步
}
// 更好的做法:只锁定需要同步的部分
public void findAndUpdateUser(String userId, Consumer<User> update) {
User user = findUserById(userId); // 不需要同步的耗时操作
synchronized(this) {
update.accept(user); // 只锁定需要同步的部分
}
}
- 使用读写锁(
ReadWriteLock)- 读操作之间不需要互斥,提高并发性能
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
public Data read() {
readLock.lock();
try {
return doRead();
} finally {
readLock.unlock();
}
}
public void write(Data data) {
writeLock.lock();
try {
doWrite(data);
} finally {
writeLock.unlock();
}
}
- 使用
StampedLock- Java 8引入的更高性能锁,支持乐观读
- 适用于读多写少的场景
private final StampedLock lock = new StampedLock();
public double read() {
// 乐观读
long stamp = lock.tryOptimisticRead();
double value = currentValue;
// 检查读取期间是否有写操作
if (!lock.validate(stamp)) {
// 如果有,进行悲观读锁定
stamp = lock.readLock();
try {
value = currentValue;
} finally {
lock.unlockRead(stamp);
}
}
return value;
}
3.2 避免不必要的共享
-
使用线程本地存储
- 使用
ThreadLocal避免共享
- 使用
-
使用副本而非共享
- 使用
CopyOnWriteArrayList等实现读多写少的场景
- 使用
3.3 并行计算框架
使用Java 8+提供的并行流和CompletableFuture,简化并行计算。
// 并行流示例
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.mapToInt(Integer::intValue)
.sum();
// CompletableFuture示例
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> getDataFromService1());
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> getDataFromService2());
CompletableFuture<String> combinedResult = future1.thenCombine(future2,
(result1, result2) -> result1 + result2);
3.4 使用正确的并发级别
CPU密集型任务:线程数 = CPU核心数 ± 1I/O密集型任务:线程数 = CPU核心数 * (1 + 等待时间/计算时间)
// 获取可用处理器数量作为线程池大小的参考
int availableProcessors = Runtime.getRuntime().availableProcessors();
// 创建适合CPU密集型任务的线程池
ExecutorService executorForCpuTasks =
Executors.newFixedThreadPool(availableProcessors);
// 创建适合I/O密集型任务的线程池(假设I/O等待时间是计算时间的4倍)
ExecutorService executorForIoTasks =
Executors.newFixedThreadPool(availableProcessors * 5);
4. 常见并发问题排查方法
高效排查并发问题需要系统性的方法和工具支持。
4.1 常见并发问题类型
-
竞态条件(
Race Conditions)- 问题:多线程同时访问共享资源导致不一致
- 解决:使用同步机制确保互斥访问
-
死锁(
Deadlocks)- 问题:线程互相等待对方持有的锁
- 解决:按顺序获取锁,使用超时机制
-
活锁(
Livelocks)- 问题:线程不断相互响应,但无法继续执行
- 解决:引入随机等待时间
-
线程饥饿(
Thread Starvation)- 问题:某些线程无法获得足够资源
- 解决:使用公平锁,避免优先级设置
4.2 问题排查工具
-
线程转储(
Thread Dumps)- 使用
jstack <pid>命令生成线程转储 - 分析线程状态、锁持有情况、等待链
- 使用
-
VisualVM和JConsole
- 可视化监控工具,查看线程状态
- 监控线程创建和资源使用
-
Java Flight Recorder (JFR)
- 低开销性能分析工具
- 捕获线程竞争、锁争用等事件
# 生成线程转储
jstack <pid> > thread_dump.txt
# 启动JFR记录
jcmd <pid> JFR.start name=MyRecording settings=default duration=60s filename=myrecording.jfr
4.3 日志和断言
在关键位置添加日志和断言,辅助排查并发问题。
// 使用日志记录线程操作
Logger logger = Logger.getLogger(MyClass.class.getName());
public synchronized void updateValue(int newValue) {
logger.fine("Thread " + Thread.currentThread().getName() +
" updating value from " + this.value + " to " + newValue);
this.value = newValue;
}
// 使用断言验证不变量
public void processData() {
synchronized(lock) {
// ... 处理数据 ...
// 验证处理后的状态是否符合预期
assert checkInvariants() : "Data invariants violated";
}
}
4.4 使用并发调试工具
-
FindBugs/SpotBugs
- 静态分析工具,检测潜在的并发问题
-
Java Pathfinder
- NASA开发的验证工具,查找并发bug
-
Thread Sanitizer
- 检测数据竞争的工具
5. 并发编程的设计模式
并发设计模式是解决特定并发问题的通用方案。
5.1 监视器模式(Monitor Pattern)
使用对象内部锁实现同步。Java的synchronized关键字就是监视器模式的实现。
public class Counter {
private int count;
// 使用synchronized方法实现监视器模式
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
5.2 读-写锁模式(Read-Write Lock Pattern)
允许多个读操作并发执行,但写操作需要独占访问。
public class ReadWriteMap<K, V> {
private final Map<K, V> map;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
public ReadWriteMap(Map<K, V> map) {
this.map = map;
}
public V get(K key) {
readLock.lock();
try {
return map.get(key);
} finally {
readLock.unlock();
}
}
public V put(K key, V value) {
writeLock.lock();
try {
return map.put(key, value);
} finally {
writeLock.unlock();
}
}
}
5.3 不可变对象模式(Immutable Object Pattern)
创建不可变对象,无需同步即可安全共享。
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
// 修改操作返回新对象,不修改原对象
public Point translate(int dx, int dy) {
return new Point(x + dx, y + dy);
}
}
5.4 生产者-消费者模式(Producer-Consumer Pattern)
使用阻塞队列协调生产者和消费者线程。
public class ProducerConsumerExample {
private static BlockingQueue<Task> queue = new LinkedBlockingQueue<>(10);
static class Producer implements Runnable {
@Override
public void run() {
try {
while (true) {
Task task = createTask();
queue.put(task); // 阻塞直到队列有空间
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private Task createTask() {
// 创建任务
return new Task();
}
}
static class Consumer implements Runnable {
@Override
public void run() {
try {
while (true) {
Task task = queue.take(); // 阻塞直到队列有元素
processTask(task);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void processTask(Task task) {
// 处理任务
}
}
static class Task {
// 任务定义
}
}
5.5 线程池模式(Thread Pool Pattern)
重用线程以减少创建和销毁线程的开销。
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
try {
// 提交多个任务
for (int i = 0; i < 100; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " executed by " +
Thread.currentThread().getName());
});
}
} finally {
// 关闭线程池
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
}
5.6 Future模式(Future Pattern)
异步计算结果的占位符。
public class FutureExample {
private final ExecutorService executor = Executors.newFixedThreadPool(4);
public Future<Integer> calculateAsync() {
return executor.submit(() -> {
// 模拟长时间计算
Thread.sleep(2000);
return 42;
});
}
public void example() throws Exception {
Future<Integer> future = calculateAsync();
// 做其他的事情...
// 获取结果(阻塞直到计算完成)
Integer result = future.get();
System.out.println("Result: " + result);
}
}
5.7 双检锁模式(Double-Checked Locking Pattern)
减少同步开销的单例实现模式。
public class Singleton {
// volatile确保可见性和有序性
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
// 第一次检查 - 不需要同步
if (instance == null) {
// 同步块
synchronized (Singleton.class) {
// 第二次检查 - 防止多线程创建多个实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
总结
Java并发编程是一个复杂但重要的领域,掌握这些最佳实践可以帮助开发者创建高效、可靠的并发应用程序。关键点包括:
- 选择合适的线程安全策略,如不可变对象、线程封闭和同步机制
- 了解并预防死锁和其他并发问题
- 优化性能和可伸缩性,平衡资源使用和响应时间
- 使用合适的工具和方法排查并发问题
- 应用经典的并发设计模式解决常见问题
通过遵循这些实践,开发者可以充分利用多核处理器的能力,同时避免并发编程中的常见陷阱。
参考资源
- 《Java Concurrency in Practice》- Brian Goetz
- 《Effective Java》- Joshua Bloch
- Oracle官方Java并发文档
- Java API文档(
java.util.concurrent包)
Java并发编程最佳实践指南
260

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



