AQS的底层原理是什么

参考答案

1.   AQS是什么

2.  AQS 的内部实现

  • AQS 队列内部维护的是一个 FIFO 的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点,所以双向链表可以从任意一个节点开始很方便的访问前驱和后继。
  • 每个 Node 其实是由线程封装,当线程争抢锁失败后,会封装成 Node 加入到 ASQ 队列中。当获取锁的线程释放锁后,会从队列中唤醒一个阻塞的节点(线程)。
Node的组成:
static final class Node {
      static final Node SHARED = new Node();
      static final Node EXCLUSIVE = null;

      static final int CANCELLED =  1;
      static final int SIGNAL    = -1;
      static final int CONDITION = -2;
      static final int PROPAGATE = -3;
    volatile int waitStatus;
      volatile Node prev;//前驱节点
      volatile Node next;//后驱节点
      volatile Thread thread;//当前线程
      Node nextWaiter;//存储在condition 队列中的后继节点
      final boolean isShared() {//是否为共享锁
            return nextWaiter == SHARED;
      }
      final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
      }
      Node() {    // Used to establish initial head or SHARED marker
      }
      Node(Thread thread, Node mode) {     // Used by addWaiter 将线程构造一个节点,添加队列
            this.nextWaiter = mode;
            this.thread = thread;
      }
      Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
      }
    }

3.  释放锁以及添加线程对于队列的变化

当出现锁竞争、以及释放锁时,AQS 同步队列中的节点会发生变化。

下面我们来看添加节点的场景:

AQS的底层原理是什么

这里涉及两个变化:
  • 新的线程封装成 Node 节点,追加到同步队列中,设置 prev 节点、以及修改当前节点的前置节点的 next 节点指向自己。
  • 通过CAS讲tail重新指向新的尾部节点。head 节点表示获取锁成功的节点,当头结点在释放同步状态时,会唤醒后继节点,如果后继节点获得锁成功,会把自己设置为头结点。
节点的变化过程如下图:
AQS的底层原理是什么
这个过程中,同样涉及到两个变化:
  •  修改 head 节点指向下一个获得锁的节点。
  •  新的获得锁的节点,将 prev 的指针指向 null。
设置 head 节点不需要用 CAS,因为设置 head 节点是由获得锁的线程来完成的,而同步锁只能由一个线程获得,所以不需要 CAS 保证,只需将 head 节点设置为原首节点的后继节点,并且断开原 head 节点的 next 引用。

4.  源码分析

以 ReentrantLock 作为切入点,我们来看看,在这个场景中是如何使用 AQS 来实现线程的同步的。

4.1   ReentrantLock 的时序图

调用 ReentrantLock 中的 lock()方法,源码的调用过程我使用了时序图来展现。

AQS的底层原理是什么

4.2   ReentrantLock.lock()

 reentrantLock 获取锁的入口:
public void lock() {
  sync.lock();
}
sync 实际上是一个抽象的静态内部类,它继承了 AQS 来实现重入锁的逻辑。
我们前面说过 AQS 是一个同步队列,它能够实现线程的阻塞以及唤醒,但它并不具备业务功能,所以在不同的同步场景中,会继承 AQS 来实现对应场景的功能。
Sync 有两个具体的实现类,分别是:
  • NofairSync:表示可以存在抢占锁的功能,也就是说不管当前队列上是否存在其他线程等待,新线程都有机会抢占锁。
  • FailSync: 表示所有线程严格按照 FIFO 来获取锁。

4.3  NofairSync.lock

以非公平锁为例,来看看 lock 中的实现。
  • 非公平锁和公平锁最大的区别在于,在非公平锁中,我抢占锁的逻辑是,不管有没有线程排队,我先上来 cas去抢占。
  • CAS 成功,就表示成功获得了锁。
  • CAS 失败,调用 acquire(1)走锁竞争逻辑。
final void lock() {
  if (compareAndSetState(0, 1))
    setExclusiveOwnerThread(Thread.currentThread());
  else
    acquire(1);
}

以上,是Java面试题【AQS的底层原理是什么】的参考答案。

 

输出,是最好的学习方法

欢迎在评论区留下你的问题、笔记或知识点补充~

—end—

👇阅读作者更多技术干货👇

阿里架构 |双11秒杀 |分布式架构 |负载均衡 |单点登录 |微服务 |云原生 |高并发 |架构师

以上

关注作者「mikechen」公众号,获取更多技术干货!

后台回复架构,即可获取《阿里架构师进阶专题全部合集》,后台回复面试即可获取《史上最全阿里Java面试题总结

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧