线程安全与并发锁:synchronized vs ReentrantLock——面试必问!
一、问题现场还原那是一个周五的下午小王正在写一个计数器public class Counter { private int count 0; public void increment() { count; // 自增 } public int getCount() { return count; } }测试代码public class CounterTest { public static void main(String[] args) throws InterruptedException { Counter counter new Counter(); // 创建1000个线程每个线程自增1000次 for (int i 0; i 1000; i) { new Thread(() - { for (int j 0; j 1000; j) { counter.increment(); } }).start(); } Thread.sleep(3000); // 等待所有线程完成 // 期望结果1000 * 1000 1000000 // 实际结果可能是 998456、998789、999234... System.out.println(最终结果 counter.getCount()); } }问题分析为什么结果不对count 实际上分为三步 1. 读取count的值 2. count 1 3. 写入新的值 线程A和线程B同时执行 ┌─────────┬─────────┐ │ 线程A │ 线程B │ ├─────────┼─────────┤ │ 读: 0 │ │ │ │ 读: 0 │ ← 读取的都是0 │ 写: 1 │ │ │ │ 写: 1 │ ← 都写入1 └─────────┴─────────┘ 结果应该是2实际是1二、解决方案使用锁2.1 synchronized关键字public class Counter { private int count 0; // 方法锁 public synchronized void increment() { count; } public synchronized int getCount() { return count; } }或者public class Counter { private int count 0; private final Object lock new Object(); // 锁对象 public void increment() { synchronized (lock) { // 代码块锁 count; } } public int getCount() { synchronized (lock) { return count; } } }2.2 ReentrantLockimport java.util.concurrent.locks.ReentrantLock; public class Counter { private int count 0; private final ReentrantLock lock new ReentrantLock(); public void increment() { lock.lock(); try { count; } finally { lock.unlock(); // 必须在finally中释放 } } public int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } }三、synchronized vs ReentrantLock对比维度synchronizedReentrantLock实现方式JVM层面关键字API层面类锁类型非公平锁可选公平/非公平锁获取锁自动释放必须手动释放超时不支持支持可中断不支持支持条件变量1个wait/notify多个Condition性能JDK 1.6后优化差不多使用场景简单场景复杂场景四、synchronized详解4.1 三种使用方式public class SynchronizedDemo { // 1. 实例方法锁锁住当前对象 public synchronized void method1() { // 代码 } // 2. 静态方法锁锁住Class对象 public static synchronized void method2() { // 代码 } // 3. 代码块锁锁住指定对象 private final Object lock new Object(); public void method3() { synchronized (lock) { // 代码 } } }4.2 锁升级JDK 1.6优化无锁 → 偏向锁 → 轻量级锁 → 重量级锁 无锁没有线程竞争 偏向锁只有一个线程访问自动偏向 轻量级锁少量线程竞争CAS自旋 重量级锁大量线程竞争等待4.3 synchronized的缺点// ❌ 缺点1无法超时 public void method() { synchronized (this) { // 如果获取不到锁会一直等待 } } // ❌ 缺点2无法中断 public void method() { synchronized (this) { // 等待期间无法被中断 } } // ❌ 缺点3只能有一个Condition public class ProducerConsumer { private int count 0; public synchronized void produce() throws InterruptedException { while (count 10) { wait(); // 生产者等待 } count; notifyAll(); // 通知消费者 } public synchronized void consume() throws InterruptedException { while (count 0) { wait(); // 消费者等待 } count--; notifyAll(); // 通知生产者 } // 问题notifyAll会唤醒所有线程包括其他不相关的 }五、ReentrantLock详解5.1 基础用法public class ReentrantLockDemo { private final ReentrantLock lock new ReentrantLock(); public void method() { lock.lock(); try { // 业务代码 } finally { lock.unlock(); // 必须在finally中释放 } } }5.2 公平锁 vs 非公平锁// 非公平锁默认性能高可能饥饿 ReentrantLock lock1 new ReentrantLock(false); // 公平锁性能略低保证公平性 ReentrantLock lock2 new ReentrantLock(true);5.3 tryLock超时获取public boolean tryLockWithTimeout() throws InterruptedException { // 尝试获取锁最多等待1秒 if (lock.tryLock(1, TimeUnit.SECONDS)) { try { // 获取成功 return true; } finally { lock.unlock(); } } else { // 获取失败 return false; } }5.4 lockInterruptibly可中断public void method() throws InterruptedException { lock.lockInterruptibly(); // 可中断的获取锁 try { // 业务代码 } finally { lock.unlock(); } }5.5 Condition条件变量public class ProducerConsumerWithLock { private final ReentrantLock lock new ReentrantLock(); private final Condition notFull lock.newCondition(); // 未满条件 private final Condition notEmpty lock.newCondition(); // 非空条件 private int count 0; public void produce() throws InterruptedException { lock.lock(); try { while (count 10) { notFull.await(); // 等待未满 } count; System.out.println(生产 count); notEmpty.signal(); // 通知消费者 } finally { lock.unlock(); } } public void consume() throws InterruptedException { lock.lock(); try { while (count 0) { notEmpty.await(); // 等待非空 } count--; System.out.println(消费 count); notFull.signal(); // 通知生产者 } finally { lock.unlock(); } } }六、性能对比6.1 基准测试public class LockPerformanceTest { private static final int THREAD_COUNT 10; private static final int INCREMENT_COUNT 1000000; Test public void testSynchronized() throws InterruptedException { Counter counter new SynchronizedCounter(); long start System.currentTimeMillis(); for (int i 0; i THREAD_COUNT; i) { new Thread(() - { for (int j 0; j INCREMENT_COUNT; j) { counter.increment(); } }).start(); } Thread.sleep(5000); System.out.println(synchronized: (System.currentTimeMillis() - start) ms); } Test public void testReentrantLock() throws InterruptedException { Counter counter new ReentrantLockCounter(); long start System.currentTimeMillis(); for (int i 0; i THREAD_COUNT; i) { new Thread(() - { for (int j 0; j INCREMENT_COUNT; j) { counter.increment(); } }).start(); } Thread.sleep(5000); System.out.println(ReentrantLock: (System.currentTimeMillis() - start) ms); } }结果JDK 1.8synchronized1200msReentrantLock1150ms性能差不多七、使用建议使用synchronized ✅ 简单场景 ✅ 不需要高级功能超时、可中断、多Condition ✅ 代码简洁 使用ReentrantLock ✅ 需要公平锁 ✅ 需要超时获取锁 ✅ 需要可中断 ✅ 需要多个Condition ✅ 需要获取锁状态八、常见问题Q1为什么synchronized不需要手动释放锁// synchronized由JVM自动管理 // 方法结束或异常时JVM自动释放锁 public synchronized void method() { // 即使抛异常也会自动释放 if (someCondition) { throw new RuntimeException(); } } // ReentrantLock必须手动释放 public void method() { lock.lock(); try { if (someCondition) { throw new RuntimeException(); } } finally { lock.unlock(); // 必须在finally中释放 } }Q2什么是锁的可见性// 可见性一个线程修改了变量其他线程能立即看到 public class VisibilityDemo { private boolean flag false; public void setFlag() { flag true; // 线程A修改 } public boolean getFlag() { return flag; // 线程B可能看不到修改 } } // 解决方案使用volatile或synchronized public class VisibilityDemo { private volatile boolean flag false; // volatile保证可见性 // 或者 public synchronized void setFlag() { flag true; } public synchronized boolean getFlag() { return flag; } }九、总结今天我们学到了要点说明线程安全问题多线程并发访问共享变量导致结果不一致synchronized关键字JVM实现简单易用ReentrantLockAPI实现功能强大使用复杂核心区别自动释放vs手动释放、1个Conditionvs多个Condition性能JDK 1.6后两者性能差不多选择简单场景用synchronized复杂场景用ReentrantLock今日互动你在项目中遇到过线程安全问题吗是用synchronized还是ReentrantLock