什么是 Java 中的线程同步?
Java线程同步深度解析
(结合2025年大厂面试高频考点与最新技术趋势)
- 核心定义与必要性
线程同步(Thread Synchronization):
在多线程环境下,通过特定机制确保多个线程对共享资源的有序访问,防止因并发操作导致的数据不一致、竞态条件(Race Condition)等问题。
必要性示例:
// 典型线程不安全场景:银行账户扣款
public class Account {
private int balance = 1000;
public void withdraw(int amount) {
if (balance >= amount) {
balance -= amount; // 此处可能被多个线程同时打断
}
}
}
若两个线程同时执行withdraw(600)
,最终余额可能为负数(如线程1读取balance=1000后未扣款时被中断,线程2同样读取1000并完成扣款)。
- 同步机制分类与实现
(1) 内置锁(synchronized)
-
方法级锁:
public synchronized void withdraw(int amount) { ... }
锁对象为当前实例(或Class对象,若为静态方法)。
-
代码块锁:
public void withdraw(int amount) { synchronized(this) { ... } // 显式指定锁对象 }
(2) Lock接口(显式锁)
private final ReentrantLock lock = new ReentrantLock();
public void withdraw(int amount) {
lock.lock();
try {
if (balance >= amount) balance -= amount;
} finally {
lock.unlock(); // 确保释放锁
}
}
优势:
- 支持公平锁(Fairness Policy)
- 可中断等待(
lockInterruptibly()
) - 超时获取锁(
tryLock(long time, TimeUnit unit)
)
(3) volatile变量
- 特性:保证可见性(Visibility)和禁止指令重排序,但不保证原子性。
- 适用场景:单线程写、多线程读的标志位控制。
private volatile boolean isRunning = true;
(4) 原子类(java.util.concurrent.atomic)
- 原理:基于CAS(Compare-And-Swap)实现无锁并发。
- 示例:
private AtomicInteger balance = new AtomicInteger(1000); public void withdraw(int amount) { balance.getAndUpdate(current -> current >= amount ? current - amount : current); }
(5) 并发容器与工具
- Collections.synchronizedXXX:线程安全集合(粗粒度锁)
- ConcurrentHashMap:分段锁(JDK8后改为CAS+synchronized优化)
- CountDownLatch/CyclicBarrier:线程协作同步
- 同步机制选择策略(2025大厂实践)
| 场景 | 推荐方案 | 理由 |
|-------------------------|---------------------------------------|-------------------------------------------|
| 高竞争环境 | ReentrantLock + Condition队列 | 细粒度控制,避免线程饥饿 |
| 简单计数器 | AtomicLong/AtomicInteger | 无锁化,性能优于synchronized |
| 读多写少 | ReadWriteLock | 读写分离,提升吞吐量 |
| 分布式环境 | Redis分布式锁(Redisson实现) | 跨JVM同步(补充Java内置锁的局限性) |
- 高频面试追问与应答技巧
追问1:synchronized和ReentrantLock的区别?
- 标准答案:
① 锁获取方式(内置语法 vs 显式API)
② 功能扩展性(不可中断 vs 支持tryLock)
③ 性能差异(JDK6后synchronized优化后差距缩小)
④ 锁实现(对象监视器 vs AQS队列)
追问2:什么是CAS?它的ABA问题如何解决?
- 应答要点:
- CAS原理:
Unsafe.compareAndSwapInt()
底层实现 - ABA问题:通过版本号(AtomicStampedReference)解决
- CAS原理:
追问3:如何检测和避免死锁?
- 解决方案:
① 使用jstack
或VisualVM分析线程栈
② 统一锁获取顺序
③ 设置超时(tryLock
)
- 最新技术趋势补充
- 协程(Loom项目):Java 21+虚拟线程(Virtual Threads)对同步机制的影响
- 无锁编程(Disruptor框架):环形缓冲区在高并发场景的应用
- 内存一致性模型(JEP 423):Java 18对内存屏障的优化
总结:面试官评估维度
- 原理深度:能否解释synchronized的锁升级过程(偏向锁→轻量级锁→重量级锁)
- 实战经验:是否遇到过线程安全导致的线上事故及修复方案
- 技术视野:对无锁编程、分布式锁等扩展方案的了解
// 附加代码:锁升级观察(通过-XX:+PrintFlagsFinal查看)
Object lock = new Object();
synchronized(lock) {
// 首次进入:偏向锁
// 竞争加剧:膨胀为轻量级锁(自旋)
// 持续竞争:最终升级为重量级锁(OS互斥量)
}