Lock vs Synchorized
Lock
- 超时获取
- 可中断获取
- 非阻塞获取
AbstractQueuedSynchronizer
Lock 的实现基础,实现了基于队列的锁的等待。竞争锁失败的线程会进去队列中等待被唤醒重新竞争锁。
acquire/release
以对一个int 值做CAS 操作来实现锁的获取和释放
private volatile int state;
AQS准备了一下模板方法供子类实现
独享锁,同时只有一个线程获取
boolean tryAcquire(int arg);
boolean tryRelease(int arg);
共享锁
int tryAcquireShared(int arg)
boolean tryReleaseShared(int arg
队列
双向队列,shuang每个node记录了对应的queue
private transient volatile Node head;
private transient volatile Node tail;
class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
}
Node Status 有以下几种
- 0 初始状态
- CANCELLED = 1
- SINAL = -1 后面的Node在等待唤醒
- PROGAGATE = -2 需要再继续唤醒后面的Node (shared lock用)
- CONDITION = -3 condition用的node
Exclusive Mode
Acquire
- 先尝试
tryAcquire
。若成功则获取锁,失败则进入队列。 - 创建Node, 自旋CAS加入队尾
- 改变前一个 Node 的状态为 Signal。如果前一个的状态为 CANCELLED, 则跳过前一个直到前一个node 为非CANCELLED 状态。
- 如果是当前队列的第二个node, 则再尝试获取锁
tryAcquire
, 以免加入队列完成前,前一个node刚好被释放 - park 当前线程
Release
- 尝试
tryRelease
,成功后,unpark 下一个node 对应的线程。 - 被唤醒的线程检查
tryAcquire
获取锁,并将 Head 设置为自己。
Shared Mode
Shared mode 获取锁的部分和 Exclusive mode 是一样的,对于state 和队列的CAS操作保证了在多个线程同时获取锁的情况下的正确性。
但是释放锁的时候,却可能出现多个线程同时释放的情况。所以就需要能将多重释放在队列里传递下去,唤醒尽可能多的线程。
Acquire
与独占模式基本一样
Release
释放锁的线程
-
tryReleaseShared
成功后 - 如果
head.watiStatus == SIGNAL
。reset head.watiStatus = 0
, 唤醒下一个node - 否则如果
head.waitStatus == 0
,set head.waitStatus = PROPAGATE
- 如果发现 head 已经改变则重复2,3
被唤醒的线程
-
tryAcquireShared
获取锁。 - 设置自己为新的head, 当tryAcquireShared 返回>0 或者 旧的head 和新的head 任意一个状态 < 0 (为SIGNAL/PROPAGATE)时。则继续尝试唤醒下一个node。
这样我们保障了
- 如果tryAcquireShared > 0, 直接说明可以获取多个资源,则尝试继续释放。
- 如果tryAcquireShared == 0,但是接下来有释放,则旧head.status = PROPAGATE,会尝试继续释放。
利用AQS的实现
ReentrantLock
tryAcquire 的时候,检查持有锁的是否为当前线程,是的话,则 state + 1,获取锁。
其他线程只有在 state == 0 的时候才能获取锁。
Fair/Unfair
在锁被释放的时候,可能存在其他线程和队列中的线程竞争锁的情况。
- Fair 遵守先来后到,让队列中的线程先获取锁。
- Unfair 共同竞争,大概率会出现刚释放锁的线程又再次获得锁。高吞度量
实现
Fair 版本在获取锁的时候,检查队列中是否有排在自己之前的node, 且线程不为自己。
ReentrantReadWriteLock
同一个state, 用高位代表读,低位代表写
Read Lock
shared mode 用来获取/释放读锁,如果当前
- 没有写锁
- 当前有写锁,但处在同一线程
则可以获取
Write Lock
Exclusive mode 用来获取/释放写锁。可重入。
利用AQS 巧妙地实现了 write lock 对于read lock 的屏障作用。当read lock释放唤醒write lock的线程时,由于write node 处于独占模式,不会讲释放propagate 给接下来的node。
write lock 降级
允许下列操作
write.lock()
read.lock()
write.unlock()
read.unlock
Condition
Lock.newCondition().await()/signal()
对应 Object.wait()/notify()
。
不同的是 condition 可以
- 同一个lock 可以有多个 condition.
- sinal 会唤醒等待最久的线程,notify 则是随机唤醒。
- 可以选择不响应中断
ConditionObject
condition 队列, 复用AQS的node
private transient Node firstWaiter;
private transient Node lastWaiter;
await
- 生成node,node.ws = CONDITION, 放入condition 队列。
- 释放锁。park 住当前线程等待被释放。
signal
- reset node.ws = 0
- 将condition 队首的node 移动到对应 lock 的 AQS 队列的末尾。
- unpark 该线程,线程检查自己已经处于AQS队列,重新进入 AQS 的等待程序。