一、问题现场还原
那是一个周五的下午,小王正在写一个计数器:
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 ReentrantLock
import 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对比
| 维度 | synchronized | ReentrantLock |
|---|---|---|
| 实现方式 | 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.8):
- synchronized:1200ms
- ReentrantLock:1150ms
- 性能差不多
七、使用建议
使用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实现,简单易用 |
| ReentrantLock | API实现,功能强大,使用复杂 |
| 核心区别 | 自动释放vs手动释放、1个Conditionvs多个Condition |
| 性能 | JDK 1.6后两者性能差不多 |
| 选择 | 简单场景用synchronized,复杂场景用ReentrantLock |
今日互动:
你在项目中遇到过线程安全问题吗?是用synchronized还是ReentrantLock?