Java 并发编程学习笔记:锁的进化与 AQS
1. 回顾:JMM 三大核心问题
我们在前半部分解决了并发编程的基础难题:
-
可见性 (Visibility):用
volatile解决。 -
原子性 (Atomicity):用
synchronized、CAS 或Atomic类解决。 -
有序性 (Ordering):用
volatile(Happens-Before 原则) 解决。
2. 为什么我们需要 ReentrantLock?
虽然 synchronized 也是一种可重入锁,但它在处理复杂并发场景时显得过于“死板”。
synchronized 的局限性(“身不由己”):
-
无法中断:一旦进入阻塞队列,就无法响应中断信号,必须死等。
-
无法超时:不能设置等待时间(如“只等5秒”)。
-
无法判断:无法知道锁的状态或队列情况。
-
阻塞机制:没抢到锁的线程会进入 阻塞态 (Blocked),虽然不消耗 CPU,但唤醒成本高。
ReentrantLock 的“超能力”:
-
灵活拿锁:
-
tryLock():尝试一下,拿不到就走(避免死锁)。 -
tryLock(time):设置超时时间(快速失败)。 -
lockInterruptibly():等待时可以被中断(响应取消操作)。
-
-
异常处理:必须遵循
try-finally模式,确保在finally块中释放锁 (unlock())。
3. 公平锁 vs. 非公平锁
ReentrantLock 构造函数可以指定 fair 参数。
-
公平锁 (Fair):严格遵守“先来后到”排队。
-
缺点:性能较差,因为频繁挂起和唤醒线程会带来昂贵的上下文切换开销。
-
-
非公平锁 (Unfair):默认策略。允许新来的线程“插队”抢锁。
-
原理:利用唤醒队列头部线程的“时间差”,让正处于运行态的新线程先执行,提高吞吐量。
-
代价:极端情况下可能导致队列里的线程 饥饿 (Starvation)。
-
4. 锁的引擎:AQS (AbstractQueuedSynchronizer)
它是 Java 并发包的基石,核心是一个 volatile int state 变量。
State 的含义(作为计数器):
-
State = 0: 锁是自由的,无人持有。
-
State = 1: 锁被某个线程持有了。
-
State > 1: 可重入 (Reentrancy) 发生。
-
同一个线程多次获得同一把锁,
state累加。 -
解锁逻辑:“谁上的锁谁解”,调用
unlock()时state减 1。 -
只有当
state减回到 0 时,锁才算彻底释放,AQS 才会唤醒下一个等待的线程。
-
更多推荐
所有评论(0)