Java锁机制
Java 的并发同步手段分两层:synchronized 是 JVM 内置的隐式锁,java.util.concurrent.locks 的 Lock 是 JDK 提供的显式锁,CAS 是无锁方案,AQS 是它们共同的底层框架。理解这四个概念的关系,就理解了 JUC 包的设计骨架:ReentrantLock、Semaphore、CountDownLatch 都建立在 AQS 之上,而 AQS 的核心操作就是 CAS。
synchronized
synchronized 是 JVM 层的内置锁,锁对象是任意 Java 对象,用 monitorenter/monitorexit 字节码实现(见 Java字节码)。三种用法:
// 1. 修饰实例方法:锁 this
public synchronized void methodA() { }
// 2. 修饰静态方法:锁 Class 对象
public static synchronized void methodB() { }
// 3. 同步代码块:锁指定对象
public void methodC() {
synchronized (lockObject) {
// 临界区
}
}现代 JVM 对 synchronized 做了锁升级优化(偏向锁→轻量级锁→重量级锁,见 JVM 锁优化章节),锁竞争不激烈时 synchronized 和 ReentrantLock 性能几乎无差别。synchronized 的局限:不可中断、不可超时、不支持公平锁、获取锁后无法主动释放。
ReentrantLock
ReentrantLock 是 JDK 提供的显式锁,弥补 synchronized 的全部局限,需要手动加锁解锁。Reentrant 意为"可重入"——同一线程可重复获取同一把锁。
Lock lock = new ReentrantLock(); // 默认非公平锁
// 或 new ReentrantLock(true) 公平锁
lock.lock(); // 获取锁(阻塞)
try {
// 临界区
} finally {
lock.unlock(); // 必须 finally 释放
}| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 可中断 | ❌ | ✅ lockInterruptibly() |
| 可超时 | ❌ | ✅ tryLock(1, TimeUnit.SECONDS) |
| 公平锁 | ❌ | ✅ 构造参数 |
| 多个等待条件 | ❌ | ✅ newCondition() 多条件队列 |
| 自动释放 | ✅ | ❌ 必须手动 unlock |
手动解锁的代价
ReentrantLock 忘记 unlock() 会永久占用锁,程序卡死且无任何报错——这是它不如 synchronized 安全的地方。能用 synchronized 就用 synchronized,需要超时/可中断/多条件时才用 ReentrantLock。
CAS
CAS(Compare And Swap,比较并交换)是无锁并发的基础操作:只有当前值等于预期值才更新,否则失败重试。它由 CPU 原子指令保证,避免加锁。
// AtomicInteger 的 incrementAndGet 底层就是 CAS
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 线程安全自增,无需锁CAS 的三个问题
- ABA 问题:值从 A 变 B 又变回 A,CAS 误判未修改——用
AtomicStampedReference加版本号解决 - 自旋开销:高竞争下反复失败重试,白白消耗 CPU——竞争激烈时反而不如锁
- 只能保证一个变量:多个变量需要组合原子更新时 CAS 无能为力
CAS 适用"竞争不激烈、操作极短"的场景(计数器、状态位),激烈竞争用锁更合适。
AQS
AQS(AbstractQueuedSynchronizer)是 JUC 锁和同步器的公共骨架:用 volatile 状态位 + CLH 等待队列 + CAS 封装出一套"获取/释放"模板。子类只需实现 tryAcquire/tryRelease 决定"什么条件算获取成功",排队、唤醒、中断都由 AQS 完成。
| 同步器 | state 含义 | 典型用途 |
|---|---|---|
ReentrantLock | 持有锁的线程数(重入计数) | 互斥访问 |
Semaphore | 剩余许可数 | 限流、控制并发数 |
CountDownLatch | 剩余未完成计数 | 等 N 个任务完成 |
// CountDownLatch:主线程等所有子任务完成
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
// 执行任务
latch.countDown(); // 完成一个减一
}).start();
}
latch.await(); // 主线程阻塞到计数为 0并发容器
并发容器解决"普通集合 + 手动同步"的性能和正确性问题,按读写模式选型:
| 容器 | 替代 | 机制 | 适用场景 |
|---|---|---|---|
ConcurrentHashMap | HashMap/Hashtable | CAS + 分段锁(JDK8 为 CAS+synchronized 桶) | 高并发读写,禁用 null |
CopyOnWriteArrayList | ArrayList | 写时复制,读无锁 | 读多写极少 |
ConcurrentLinkedQueue | LinkedList | CAS 无锁队列 | 高并发队列 |
ConcurrentSkipListMap | TreeMap | 跳表,有序 | 高并发有序 Map |
| 阻塞队列家族 | — | 阻塞式存取 | 生产者-消费者(见 Java并发编程) |
// CopyOnWriteArrayList:每次修改复制整个数组,读线程永不阻塞
List<String> list = new CopyOnWriteArrayList<>();
list.add("a"); // 写:复制数组
String s = list.get(0); // 读:无锁直接读读多写少的真相
CopyOnWriteArrayList 适合"读多写极少"(如配置缓存、监听器列表);写入频繁时每次复制数组成本很高,退化为性能灾难。
锁机制与 JVM 的锁升级、Java字节码 的 monitorenter 指令、Java并发编程 的并发基础、Java线程池 的线程池配置构成 Java 并发的完整知识链。