Java并发安全解析
一、前言:为什么并发安全如此重要?
在当今高并发的互联网时代,多线程编程已经成为Java开发者的必备技能。然而,并发编程在提升系统性能的同时,也带来了复杂的安全性问题。据统计,约70%的生产环境问题与并发安全相关,且这类问题往往难以复现和调试。掌握并发安全知识,不仅是为了写出正确的代码,更是保障系统稳定性的关键。
二、并发安全的三大核心问题
2.1 原子性问题
一个或多个操作要么全部执行成功,要么全部不执行,中间不能被中断。
典型例子: i++操作不是原子操作,它包含读取、增加、写入三个步骤。
2.2 可见性问题
一个线程对共享变量的修改,其他线程能够立即看到。
原因: CPU缓存、指令重排序等优化导致。
2.3 有序性问题
程序执行的顺序不一定按照代码的先后顺序执行。
原因: 编译器和处理器会对指令进行重排序优化。
三、Java内存模型(JMM)与并发安全
3.1 JMM核心概念
Java内存模型定义了线程如何与内存进行交互,它解决了可见性和有序性问题。
// Happens-Before规则示例
public class JMMExample {private int x = 0;private volatile boolean flag = false;public void writer() {x = 42; // 操作1flag = true; // 操作2(volatile写)}public void reader() {if (flag) { // 操作3(volatile读)System.out.println(x); // 操作4}}
}
3.2 Happens-Before规则
程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作
volatile规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读
传递性规则:如果A happens-before B,且B happens-before C,那么A happens-before C
四、同步机制深度解析
4.1 synchronized实现原理
4.1.1 对象头结构
// 对象内存布局
|--------------------------------------------------------------------------|
| Mark Word (64 bits) | Klass Word (64 bits) | Instance Data |
|--------------------------------------------------------------------------|
| 锁状态信息、hashCode、分代年龄等 | 指向类元数据的指针 | 对象实际数据 |
|--------------------------------------------------------------------------|
4.1.2 锁升级过程
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
偏向锁优化: 减少同一线程重复获取锁的开销
轻量级锁: 通过CAS操作避免线程阻塞
重量级锁: 真正的互斥锁,涉及操作系统内核态切换
4.2 ReentrantLock高级特性
public class AdvancedLockExample {private final ReentrantLock lock = new ReentrantLock(true); // 公平锁private final Condition condition = lock.newCondition();public void execute() throws InterruptedException {// 尝试获取锁,最多等待100msif (lock.tryLock(100, TimeUnit.MILLISECONDS)) {try {while (someCondition) {condition.await(); // 释放锁并等待}condition.signalAll(); // 唤醒所有等待线程} finally {lock.unlock();}}}
}
五、线程安全容器详解
5.1 ConcurrentHashMap演进
JDK 7:分段锁机制
// 分段锁实现,默认16个段
final Segment<K,V>[] segments;
JDK 8及以后:CAS + synchronized优化
// 使用Node+CAS+synchronized实现更细粒度锁
// 关键方法:putVal, spread, tabAt, casTabAt
5.2 CopyOnWrite容器
// 写时复制,适合读多写少场景
List<String> list = new CopyOnWriteArrayList<>();
Set<String> set = new CopyOnWriteArraySet<>();
5.3 并发队列对比
队列类型 | 特点 | 适用场景 |
---|---|---|
ArrayBlockingQueue | 有界阻塞队列,数组实现 | 生产者-消费者模式 |
LinkedBlockingQueue | 可选有界,链表实现 | 吞吐量要求高的场景 |
ConcurrentLinkedQueue | 无界非阻塞队列 | 高并发场景 |
PriorityBlockingQueue | 优先级阻塞队列 | 任务调度 |
SynchronousQueue | 不存储元素的阻塞队列 | 任务传递 |
六、原子类与CAS原理
6.1 CAS(Compare And Swap)机制
// Unsafe类中的CAS操作
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
6.2 原子类体系
// 基本类型
AtomicInteger atomicInt = new AtomicInteger(0);
AtomicLong atomicLong = new AtomicLong(0L);
AtomicBoolean atomicBoolean = new AtomicBoolean(false);// 引用类型
AtomicReference<String> atomicRef = new AtomicReference<>();
AtomicStampedReference<String> stampedRef = new AtomicStampedReference<>("initial", 0);// 数组类型
AtomicIntegerArray atomicArray = new AtomicIntegerArray(10);// 字段更新器
AtomicIntegerFieldUpdater<MyClass> updater = AtomicIntegerFieldUpdater.newUpdater(MyClass.class, "field");
6.3 ABA问题及解决方案
// 使用AtomicStampedReference解决ABA问题
AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);int[] stampHolder = new int[1];
String current = ref.get(stampHolder); // 同时获取值和版本戳ref.compareAndSet("A", "B", stampHolder[0], stampHolder[0] + 1);
七、死锁与资源竞争问题
7.1 死锁产生的四个必要条件
互斥条件:资源不能被共享,只能由一个进程使用
请求与保持条件:进程已持有至少一个资源,又提出新的资源请求
不剥夺条件:进程已获得的资源不能被剥夺,只能自愿释放
循环等待条件:存在进程资源的循环等待链
7.2 死锁预防与检测
// 使用tryLock避免死锁
public boolean transfer(Account from, Account to, int amount) {while (true) {if (from.getLock().tryLock()) {try {if (to.getLock().tryLock()) {try {// 执行转账操作return true;} finally {to.getLock().unlock();}}} finally {from.getLock().unlock();}}// 随机休眠避免活锁Thread.sleep((long) (Math.random() * 10));}
}
7.3 活锁与饥饿
活锁:线程不断重试相同的操作但总是失败
饥饿:线程长时间无法获取所需资源
八、面试常见问题与深度解答
8.1 基础概念类
Q1: volatile关键字如何保证可见性和有序性?
可见性:通过内存屏障强制刷新工作内存到主内存
有序性:禁止指令重排序,建立happens-before关系
Q2: synchronized和ReentrantLock的性能对比?
JDK 6后synchronized性能大幅提升,在大部分场景下性能相当
ReentrantLock提供更灵活的锁机制,但在高竞争环境下可能更优
8.2 实战应用类
Q3: 如何选择synchronized和Lock?
简单同步需求使用synchronized
需要高级功能(超时、中断、公平性)时使用Lock
考虑团队熟悉程度和维护成本
Q4: ConcurrentHashMap的size()方法准确性?
JDK 7的size()方法不一定完全准确,是估计值
JDK 8的size()通过baseCount和counterCells精确计算
8.3 高级原理类
Q5: 什么是伪共享?如何避免?
伪共享:多个变量存储在同一个缓存行中,导致不必要的缓存失效
解决方法:使用填充(padding)或@Contended注解
Q6: ThreadLocal内存泄漏问题如何解决?
原因:ThreadLocalMap的Entry使用弱引用,但value是强引用
解决方案:使用后及时调用remove()方法清理