synchronized 和 Lock
synchronized 和 Lock
并发编程的核心目标是“正确性 + 性能”。锁机制是实现线程安全的基石。
本文将深入解析 Java 锁的种类、原理、优化机制与最佳实践。
一、为什么需要锁
多个线程同时读写共享资源时,可能发生 竞态条件 (Race Condition)。
锁用于控制访问顺序,保证数据一致性。
示例:非线程安全的情况
class Counter {private int count = 0;public void increment() { count++; }
}
若多线程同时执行 increment(),可能出现计数不准确。
二、synchronized 的用法与原理
synchronized 是最基本的同步机制。
| 用法 | 示例 | 锁对象 |
|---|---|---|
| 修饰方法 | synchronized void add() | 当前实例 |
| 修饰静态方法 | synchronized static void add() | 当前类 Class 对象 |
| 修饰代码块 | synchronized(this) | 自定义对象 |
示例:
public synchronized void add() { count++; }public void addSyncBlock() {synchronized (this) {count++;}
}
底层对应 monitorenter / monitorexit 指令,基于对象监视器锁。
三、synchronized 的优化演进
| 版本 | 优化机制 | 说明 |
|---|---|---|
| JDK 1.5 | 偏向锁 | 无竞争场景加锁快 |
| JDK 1.6 | 轻量级锁 | CAS 操作替代重量锁 |
| JDK 1.6+ | 自旋锁、自适应锁 | 避免频繁阻塞唤醒 |
| JDK 1.7+ | 锁消除、锁粗化 | 编译器级优化 |
四、显式锁:Lock 接口家族
java.util.concurrent.locks 提供了更灵活的加锁机制。
主要类:
| 类 | 特点 |
|---|---|
| ReentrantLock | 可重入、可中断、公平锁支持 |
| ReentrantReadWriteLock | 读写分离锁,提高并发性能 |
| StampedLock | 乐观读锁,高并发性能更优 |
示例:ReentrantLock 使用
Lock lock = new ReentrantLock();
try {lock.lock();System.out.println("安全访问");
} finally {lock.unlock();
}
五、Lock 与 synchronized 对比
| 对比项 | synchronized | Lock |
|---|---|---|
| 可重入性 | ✅ 支持 | ✅ 支持 |
| 可中断 | ❌ | ✅ tryLock 支持 |
| 公平锁 | ❌ | ✅ 可选 |
| 自动释放 | ✅ | ❌ 需手动 unlock |
| 性能 | 自动优化 | 更可控但需谨慎 |
六、读写锁示例:ReentrantReadWriteLock
class RWExample {private final Map<String, String> cache = new HashMap<>();private final ReadWriteLock lock = new ReentrantReadWriteLock();public String get(String key) {lock.readLock().lock();try { return cache.get(key); }finally { lock.readLock().unlock(); }}public void put(String key, String value) {lock.writeLock().lock();try { cache.put(key, value); }finally { lock.writeLock().unlock(); }}
}
✅ 多读共享,写独占,大幅提升并发性能。
七、Lock 高级技巧:tryLock + Condition
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();lock.lock();
try {while (!ready) condition.await();// 业务逻辑condition.signalAll();
} finally {lock.unlock();
}
八、CAS 与无锁编程
CAS(Compare And Swap)是 J.U.C 包下常见的无锁机制。
依赖 CPU 原子指令完成更新,典型应用:
- AtomicInteger、AtomicLong
- ConcurrentHashMap
- LongAdder
优点:性能高,不需阻塞。
缺点:可能出现 ABA 问题(可用 AtomicStampedReference 解决)。
九、最佳实践
- ✅ 优先使用无锁或读写锁结构(如 ConcurrentHashMap)
- ✅ 使用 synchronized 时锁范围尽量小
- ✅ 保证锁的释放(finally/unlock)
- ✅ 避免嵌套锁与死锁
- ✅ 监控锁竞争与线程状态(使用 JConsole / VisualVM)
总结
| 锁类型 | 特点 | 使用场景 |
|---|---|---|
| synchronized | 简单可靠,自动释放 | 小规模同步 |
| ReentrantLock | 高级控制、可中断、公平锁 | 复杂同步逻辑 |
| ReadWriteLock | 多读少写 | 缓存、共享资源 |
| StampedLock | 乐观读 | 高并发读场景 |
