美文网首页
Java Lock (AQS)

Java Lock (AQS)

作者: lxian2shell | 来源:发表于2018-07-17 10:00 被阅读0次

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

  1. 先尝试 tryAcquire。若成功则获取锁,失败则进入队列。
  2. 创建Node, 自旋CAS加入队尾
  3. 改变前一个 Node 的状态为 Signal。如果前一个的状态为 CANCELLED, 则跳过前一个直到前一个node 为非CANCELLED 状态。
  4. 如果是当前队列的第二个node, 则再尝试获取锁 tryAcquire, 以免加入队列完成前,前一个node刚好被释放
  5. park 当前线程

Release

  1. 尝试 tryRelease,成功后,unpark 下一个node 对应的线程。
  2. 被唤醒的线程检查 tryAcquire 获取锁,并将 Head 设置为自己。

Shared Mode

Shared mode 获取锁的部分和 Exclusive mode 是一样的,对于state 和队列的CAS操作保证了在多个线程同时获取锁的情况下的正确性。
但是释放锁的时候,却可能出现多个线程同时释放的情况。所以就需要能将多重释放在队列里传递下去,唤醒尽可能多的线程。

Acquire

与独占模式基本一样

Release

释放锁的线程

  1. tryReleaseShared 成功后
  2. 如果 head.watiStatus == SIGNALreset head.watiStatus = 0, 唤醒下一个node
  3. 否则如果 head.waitStatus == 0, set head.waitStatus = PROPAGATE
  4. 如果发现 head 已经改变则重复2,3

被唤醒的线程

  1. tryAcquireShared 获取锁。
  2. 设置自己为新的head, 当tryAcquireShared 返回>0 或者 旧的head 和新的head 任意一个状态 < 0 (为SIGNAL/PROPAGATE)时。则继续尝试唤醒下一个node。

这样我们保障了

  1. 如果tryAcquireShared > 0, 直接说明可以获取多个资源,则尝试继续释放。
  2. 如果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

  1. 生成node,node.ws = CONDITION, 放入condition 队列。
  2. 释放锁。park 住当前线程等待被释放。

signal

  1. reset node.ws = 0
  2. 将condition 队首的node 移动到对应 lock 的 AQS 队列的末尾。
  3. unpark 该线程,线程检查自己已经处于AQS队列,重新进入 AQS 的等待程序。

相关文章

网友评论

      本文标题:Java Lock (AQS)

      本文链接:https://www.haomeiwen.com/subject/ojebpftx.html