Java并发编程之AQS(二)

从自定义独占锁OnlyOneLock去看AQS源码

先说明本文是基于JDK1.8的源码分析,其他版本的JDK实现可能有所不同。
在自定义锁中,我们覆盖Lock接口的方法,AQS中我们覆盖的几个方法只需要处理好拿到锁和没有拿到锁的逻辑,至于线程怎么维护,我们却不需要理会。



AQS如何实现获取锁的逻辑
我们自定义的独占锁OnlyOneLock,在我们重写了tryAcquire时,AQS的模板方法acquire内部会调用这个tryAcquire方法,然后维护队列逻辑。

OnlyOneLock类的tryLock方法
  1. @Override
  2. public boolean tryLock() {
  3. return aqs.tryAcquire(1);
  4. }


tryLock调用了AQS的tryAcquire方法。刚好我们在OnlyOneLockAQS类中重写了这个方法
  1. @Override
  2. protected boolean tryAcquire(int arg) {
  3. //状态为0时,设置为1,表示当前线程获取到锁
  4. if(compareAndSetState(0,1)){
  5. //设置当前线程为获取到锁的线程
  6. setExclusiveOwnerThread(Thread.currentThread());
  7. return true ;
  8. }
  9. return false;
  10. }


AQS的模板方法acquire内部会调用这个tryAcquire方法,然后维护队列逻辑。
  1. public final void acquire(int arg) {
  2. if (!tryAcquire(arg) &&
  3. acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //调用tryAcquire成功获取锁的话,就结束
  4. selfInterrupt(); //没有获取成功,调用addWaiter();把当前线程构建成一个Node加入到队列尾部。
  5. }
Node.EXCLUSIVE是一个null结点。因为我们这里定义的是独占锁。


上面调用了addWaiter方法。把当前线程构建成一个Node加入到队列尾部。
  1. private Node addWaiter(Node mode) {
  2. Node node = new Node(Thread.currentThread(), mode); //新的结点
  3. // Try the fast path of enq; backup to full enq on failure
  4. Node pred = tail; //tail是原队列的尾结点
  5. if (pred != null) { //原队列不为空
  6. node.prev = pred; //新节点node的前置结点是pred(原队列的尾结点)
  7. if (compareAndSetTail(pred, node)) { //连接新的尾结点
  8. pred.next = node;
  9. return node;
  10. }
  11. }
  12. enq(node); //原队列为空的情况下,初始化队列
  13. return node;
  14. }


调用addWaiter方法后,会再调用acquireQueued。
  1. final boolean acquireQueued(final Node node, int arg) {
  2. boolean failed = true;
  3. try {
  4. boolean interrupted = false;
  5. for (;;) {
  6. final Node p = node.predecessor(); //获取node结点的前一个结点p
  7. if (p == head && tryAcquire(arg)) { //如果p是首节点head并且node代表的线程获取锁成功
  8. setHead(node); //新的队首
  9. p.next = null; // help GC
  10. failed = false;
  11. return interrupted;
  12. }
  13. if (shouldParkAfterFailedAcquire(p, node) &&
  14. parkAndCheckInterrupt()) //等待
  15. interrupted = true;
  16. }
  17. } finally {
  18. if (failed)
  19. cancelAcquire(node);
  20. }
  21. }

acquireQueued方法可以总结如下:
一,先检查当前线程在队列中的前一个线程是否为头结点,如果是就用tryAcquire方法尝试再拿一次。
二,上面的尝试中没有拿到锁的话,会在parkAndCheckInterrupt时把自己给Park掉,Park大致可以理解为休眠(Sleep)
三,Park的时候,线程就老老实实的在队列里面等着,等什么呢?等获取锁的线程释放锁后通知它,它好继续在for循环里面获取锁。


自定义的独占锁AQS维护锁逻辑的大致过程流程图如下。




因为我们定义的是独占锁,如果线程请求锁直接成功,那它就不需要入队列。如果没有成功获取,就要入队列,队列的首节点永远是持有锁的那个结点。



AQS如何实现释放锁的逻辑

AQS的内部类Node有一个重要的成员变量waitStatus。该变量用来表示当前节点代表的线程的状态。初始时为0。
  1. * The field is initialized to 0 for normal sync nodes, and
  2. * CONDITION for condition nodes. It is modified using CAS
  3. * (or when possible, unconditional volatile writes).
  4. */
  5. volatile int waitStatus;

以下是他的取值可能。
  1. static final int CANCELLED = 1; //取消
  2. static final int SIGNAL = -1; //该线程需要unpark
  3. static final int CONDITION = -2; //等待某个条件
  4. static final int PROPAGATE = -3;



OnlyOneLock重写的unlock调用了AQS的release方法。
  1. @Override
  2. public void unlock() {
  3. aqs.release(1);
  4. }


AQS会调用tryRelease方法,因为我们在OnlyOneLockAQS中重写了这个方法,所以执行时,调用的是我们重写后的tryRelease方法。
  1. public final boolean release(int arg) {
  2. if (tryRelease(arg)) { //如果释放锁成功
  3. Node h = head; //队头
  4. if (h != null && h.waitStatus != 0) //waitStatus!=0说明,线程在等待获取锁
  5. unparkSuccessor(h); //唤醒后继节点
  6. return true;
  7. }
  8. return false;
  9. }


unparkSuccessor方法源码如下。
  1. private void unparkSuccessor(Node node) {
  2. int ws = node.waitStatus;
  3. if (ws < 0)
  4. compareAndSetWaitStatus(node, ws, 0);
  5. Node s = node.next;
  6. if (s == null || s.waitStatus > 0) {
  7. s = null;
  8. for (Node t = tail; t != null && t != node; t = t.prev)
  9. if (t.waitStatus <= 0)
  10. s = t;
  11. }
  12. if (s != null)
  13. LockSupport.unpark(s.thread); //唤醒后继结点
  14. }



参考:
https://juejin.im/post/5a3a09d9f265da4312810fb9
https://juejin.im/post/5a3c6aa551882538d3101d5f


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章