1. 共享资源并发风险的本质
当多个线程同时访问和修改同一个共享资源时,如果没有适当的同步机制,就会导致数据不一致、逻辑错误甚至系统崩溃。这种风险源于现代计算机体系结构中三个基本特性:
- 线程切换的不可预测性:操作系统会根据时间片轮转调度线程,我们无法预知何时会发生线程切换
- 内存可见性问题:由于CPU缓存的存在,一个线程对变量的修改可能不会立即被其他线程看到
- 指令重排序优化:编译器和处理器会调整指令执行顺序以提高性能,可能破坏代码的预期逻辑顺序
重要提示:即使看似简单的i++操作,在底层也会分解为"读取-修改-写入"三个步骤,在多线程环境下这可能导致更新丢失
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Java内存模型(JMM)与并发基础
2.1 工作内存与主内存
Java内存模型规定了所有变量都存储在主内存中,每个线程有自己的工作内存(可以理解为CPU缓存和寄存器的抽象)。线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存变量。
java复制// 典型的内存可见性问题示例
public class VisibilityProblem {
private static boolean ready = false;
private static int number = 0;
public static void main(String[] args) {
new Thread(() -> {
while(!ready) {
// 可能永远循环
}
System.out.println(number);
}).start();
number = 42;
ready = true;
}
}
2.2 happens-before原则
这是理解Java并发最核心的原则,定义了操作之间的可见性关系:
- 程序顺序规则:线程内的每个操作happens-before该线程中的任意后续操作
- 监视器锁规则:解锁操作happens-before后续的加锁操作
- volatile变量规则:写操作happens-before后续的读操作
- 线程启动规则:线程A启动线程B,那么A中的操作happens-beforeB中的任何操作
- 线程终止规则:线程A中的任何操作都happens-before线程B终止
- 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C
3. 典型并发问题场景分析
3.1 竞态条件(Race Condition)
当多个线程访问共享数据并且至少有一个线程执行写操作时,如果操作的时序影响最终结果,就存在竞态条件。
java复制// 经典的银行转账竞态条件示例
public class BankAccount {
private int balance;
public void transfer(BankAccount dest, int amount) {
if (this.balance >= amount) {
this.balance -= amount; // 可能在这里被切换
dest.balance += amount;
}
}
}
3.2 死锁(Deadlock)
四个必要条件:
- 互斥条件:资源一次只能被一个线程占用
- 占有并等待:线程持有资源并等待获取其他资源
- 不可抢占:已分配的资源不能被强制剥夺
- 循环等待:存在线程的循环等待链
java复制// 典型死锁示例
public class DeadlockDemo {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
synchronized (lock2) {
// 操作
}
}
}
public void method2() {
synchronized (lock2) {
synchronized (lock1) {
// 操作
}
}
}
}
3.3 活锁(Livelock)
线程虽然没有被阻塞,但由于不断重试相同的操作而无法继续前进。典型场景是过度设计的重试机制。
3.4 资源耗尽
线程过多导致内存耗尽,或锁竞争导致CPU资源浪费。
4. Java并发解决方案详解
4.1 synchronized关键字
最基础的同步机制,可以用于方法或代码块:
java复制// 同步方法
public synchronized void increment() {
count++;
}
// 同步代码块
public void increment() {
synchronized(this) {
count++;
}
}
注意事项:
- 锁的是对象而不是代码
- 静态同步方法锁的是Class对象
- 过度使用会导致性能问题
- 要避免锁的嵌套导致死锁
4.2 volatile关键字
保证变量的可见性和禁止指令重排序,但不保证原子性。适合作为状态标志:
java复制private volatile boolean shutdownRequested;
public void shutdown() {
shutdownRequested = true;
}
public void doWork() {
while (!shutdownRequested) {
// 执行任务
}
}
4.3 java.util.concurrent包
4.3.1 原子类(Atomic Classes)
java复制private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet(); // CAS操作实现
}
4.3.2 Lock接口
比synchronized更灵活,支持尝试获取锁、定时锁等:
java复制private final Lock lock = new ReentrantLock();
public void transfer(Account from, Account to, int amount) {
lock.lock();
try {
from.withdraw(amount);
to.deposit(amount);
} finally {
lock.unlock();
}
}
4.3.3 并发容器
- ConcurrentHashMap:分段锁实现的高并发Map
- CopyOnWriteArrayList:写时复制的List
- BlockingQueue:阻塞队列实现生产者消费者模式
4.4 线程安全设计模式
4.4.1 不可变对象
java复制public final class ImmutablePoint {
private final int x;
private final int y;
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
// 只有getter方法
}
4.4.2 线程封闭
- 栈封闭:局部变量天然线程封闭
- ThreadLocal:每个线程有自己的变量副本
java复制private static ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
5. 性能优化与最佳实践
5.1 减少锁竞争
- 缩小同步范围
- 降低锁粒度(如ConcurrentHashMap的分段锁)
- 使用读写锁(ReentrantReadWriteLock)
- 避免热点域
5.2 避免常见陷阱
- 不要在同步块中调用外部方法(可能导致死锁)
- 注意锁的顺序(预防死锁)
- 避免过度同步
- 不要依赖线程优先级
5.3 并发测试技巧
- 使用CountDownLatch协调测试线程
- 使用随机延迟暴露竞态条件
- 使用JMH进行基准测试
java复制@Benchmark
@Threads(4)
public void testConcurrentAccess() {
// 测试代码
}
6. 高级并发模式
6.1 Fork/Join框架
适合分治算法的并行计算:
java复制public class FibonacciTask extends RecursiveTask<Long> {
final long n;
FibonacciTask(long n) { this.n = n; }
protected Long compute() {
if (n <= 1) return n;
FibonacciTask f1 = new FibonacciTask(n - 1);
f1.fork();
FibonacciTask f2 = new FibonacciTask(n - 2);
return f2.compute() + f1.join();
}
}
6.2 CompletableFuture
异步编程的强大工具:
java复制CompletableFuture.supplyAsync(() -> fetchData())
.thenApply(data -> processData(data))
.thenAccept(result -> displayResult(result))
.exceptionally(ex -> {
System.err.println("Error: " + ex.getMessage());
return null;
});
6.3 反应式编程
基于事件驱动的并发模型:
java复制Flux.range(1, 10)
.parallel()
.runOn(Schedulers.parallel())
.map(i -> i * 2)
.subscribe(System.out::println);
7. 实战案例分析
7.1 高性能计数器实现
java复制public class OptimizedCounter {
private final AtomicLong[] counters;
private static final int SIZE = Runtime.getRuntime().availableProcessors();
public OptimizedCounter() {
counters = new AtomicLong[SIZE];
for (int i = 0; i < SIZE; i++) {
counters[i] = new AtomicLong();
}
}
public void increment() {
int index = (int)(Thread.currentThread().getId() % SIZE);
counters[index].incrementAndGet();
}
public long get() {
long sum = 0;
for (AtomicLong counter : counters) {
sum += counter.get();
}
return sum;
}
}
7.2 分布式锁设计
java复制public class DistributedLock {
private final ZooKeeper zk;
private final String lockPath;
public boolean tryLock(long timeout, TimeUnit unit) throws Exception {
long start = System.currentTimeMillis();
long end = start + unit.toMillis(timeout);
while (System.currentTimeMillis() < end) {
try {
zk.create(lockPath, new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
return true;
} catch (KeeperException.NodeExistsException e) {
Thread.sleep(100);
}
}
return false;
}
public void unlock() throws Exception {
zk.delete(lockPath, -1);
}
}
8. 调试与问题排查
8.1 线程转储分析
bash复制jstack <pid> > thread_dump.txt
分析要点:
- 查找BLOCKED状态的线程
- 检查锁持有者和等待者
- 识别死锁情况
8.2 JConsole与VisualVM
图形化工具可以:
- 监控线程状态
- 检测死锁
- 分析锁竞争情况
8.3 并发测试工具
- JMeter:模拟高并发请求
- JCStress:专门测试并发正确性
- ContiPerf:性能基准测试
9. Java并发演进与新特性
9.1 Java 8改进
- StampedLock:乐观读锁
- CompletableFuture增强
- 并行流(parallel streams)
9.2 Java 9-17新特性
- 响应式流API
- 虚拟线程(预览)
- 结构化并发(孵化)
java复制// 虚拟线程示例(Java 19+)
Thread.startVirtualThread(() -> {
System.out.println("Running in virtual thread");
});
10. 架构层面的并发考量
10.1 并发与CAP权衡
- 一致性(Consistency) vs 可用性(Availability)
- 最终一致性模式
- 事件溯源与CQRS
10.2 微服务并发控制
- 分布式锁实现
- 乐观并发控制
- Saga事务模式
10.3 数据库并发策略
- 隔离级别选择
- 乐观锁与悲观锁
- 行锁与表锁
sql复制-- 乐观锁实现
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = 5;
在实际项目中,我经常发现开发人员过度依赖synchronized而忽略了更高级的并发工具。特别是在处理IO密集型任务时,合理使用异步和非阻塞IO往往能获得更好的性能。另一个常见误区是认为使用并发工具就一定能提高性能,实际上错误的并发设计反而会降低性能。建议在实现前先明确并发需求的性质(CPU密集型还是IO密集型),再选择合适的并发策略。
