共享资源与竞态条件
在Java中,并发问题的核心源于多个线程同时访问共享资源,当多个线程读写同一数据时,如果缺乏适当的同步机制,就会导致竞态条件(Race Condition),即程序的执行结果依赖于线程执行的时序,从而出现不可预期的错误,两个线程同时递增一个计数器,由于指令重排序或缓存不一致,最终结果可能小于预期值,可见性问题(一个线程的修改对其他线程不可见)和原子性问题(多个操作作为一个整体被中断)也是并发编程中的常见挑战。

Java内存模型:可见性与有序性的基石
Java内存模型(JMM)是解决并发问题的关键规范,它定义了线程如何与主内存交互,确保多线程环境下的可见性和有序性。
可见性:从主内存到工作内存
JMM规定,每个线程拥有独立的工作内存(存储变量的副本),线程对变量的修改需先同步回主内存,其他线程才能读取到最新值,通过volatile关键字,可以强制变量修改后立即刷新到主内存,并且读取时直接从主内存加载,从而避免缓存不一致问题。
volatile boolean flag = false;
// 线程A
flag = true;
// 线程B
if (flag) {
// 确保读取到线程A的修改
}
有序性:禁止指令重排序
JMM通过happens-before原则约束指令执行顺序,确保特定场景下的有序性。volatile变量的写操作 happens-before 其读操作;解锁 happens-before 加锁同一把锁,这些规则避免了编译器和处理器对代码进行破坏并发逻辑的重排序。
同步机制:锁与原子类的协同
锁是最基础的同步工具,用于保护共享资源,Java提供了多种锁机制,适应不同的并发场景。
synchronized:内置的重量级锁
synchronized通过监视器(Monitor)实现原子性,确保同一时间只有一个线程进入同步代码块,它分为实例锁(作用于对象)和类锁(作用于类对象),既能保证原子性,也能通过内存语义保证可见性。
public class Counter {
private int count = 0;
public synchronized void increment() {
count++; // 原子操作
}
}
synchronized的缺点是性能开销较大(涉及用户态到内核态的切换),但在JDK 6后通过优化(如偏向锁、轻量级锁)显著提升了效率。

ReentrantLock:可重入的显式锁
java.util.concurrent.locks.ReentrantLock是synchronized的增强版,支持公平锁(按申请顺序获取锁)和非公平锁(默认,允许插队),同时提供tryLock()方法避免阻塞。
ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // 必须在finally中释放锁
}
}
原子类:无锁化的高效操作
对于简单变量的原子操作,java.util.concurrent.atomic包下的原子类(如AtomicInteger、AtomicLong)通过CAS(Compare-And-Swap)机制实现无锁并发,避免了线程上下文切换的开销。
AtomicInteger count = new AtomicInteger(0); count.incrementAndGet(); // 原子递增
CAS的原理是:如果内存值等于预期值,则更新为新值;否则重试,但需注意ABA问题(可通过AtomicStampedReference解决)。
高级并发工具:线程池与并发集合
复杂的并发场景需要更高级的工具来管理线程和数据结构。
线程池:复用线程,提升效率
频繁创建和销毁线程会带来性能损耗。java.util.concurrent.ExecutorService(如ThreadPoolExecutor)通过线程池复用线程,控制并发数,并提供任务队列(如ArrayBlockingQueue)和拒绝策略(如AbortPolicy)。
ExecutorService pool = Executors.newFixedThreadPool(5);
pool.execute(() -> {
// 任务逻辑
});
pool.shutdown();
并发集合:线程安全的数据结构
Java提供了线程安全的集合类,如ConcurrentHashMap(分段锁实现高效并发读写)、CopyOnWriteArrayList(写时复制,适合读多写少场景)和BlockingQueue(阻塞队列,用于生产者-消费者模型)。

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1); // 线程安全
最佳实践:避免死锁与优化性能
避免死锁
死锁发生在多个线程互相等待对方释放锁时,遵循以下原则可降低风险:
- 按固定顺序获取锁;
- 尽量使用
tryLock()设置超时; - 避免嵌套锁。
减少锁粒度
将大锁拆分为小锁,例如ConcurrentHashMap通过分段锁降低锁竞争。
使用不可变对象
不可变对象(如String)无需同步,天然线程安全,可减少同步开销。
Java通过JMM保证内存可见性和有序性,结合synchronized、ReentrantLock、原子类等同步机制解决并发安全问题,线程池和并发集合提供了高效的并发编程工具,在实际开发中,需根据场景选择合适的同步策略,平衡安全性与性能,避免死锁和过度同步,才能构建稳定的高并发系统。












