AQS原理大白话

57 阅读4分钟

1、定义

  • AQS就是AbstractQueuedSynchronized,抽象队列同步器,用于构建锁和同步器的框架。((ReentrantLock、ReentrantReadWriteLock 和 CountDownLatch 就实现了aqs)
  • 内部有个核心变量state表示有没有上锁,还有一个变量记录当前锁是哪个线程。还有一个等待队列专门存放加锁失败的线程的。

2、流程

2.1 初始化

  • 在AQS初始化的时候,head 和 tail都是空的。

2.1第一个线程进入

  • 当第一个线程进入的时候,也不会初始化队列。只是会exclusiveOwnerThread变为当前线程。
  • 当然具体能不能把exclusiveOwnerThread变为当前线程。

2.1 怎么让线程拿到锁->CAS

  • 先判断能不能通过CAS把STATE从0设置为1,由于CAS一次只能成功一个线程,当STATE为1或者CAS失败就去了CLH队列。

2.2 拿锁失败了去CLH 等待队列

  • CLH队列就是一个链表做的阻塞队列,用来存放竞争锁失败的队列。
  • 只有当第一个线程没有执行完,再来一个线程时。CLH队列会创建一个空节点,让head指向空节点,表示没有线程在等待获取锁,用tail指向第二个节点,其中包含第二个线程的引用。第二个节点通过pre指向前一个节点。

2.3 去了等待队列的线程怎么唤醒

  • 这个时候第二个节点会自旋两次,看前个节点的locked是否为false,如果是false,表示可以取获取锁了。如果超过两次就会被阻塞,LockSupport.park(),然后前一个节点释放锁了,LockSupport.unpark()来唤醒。

2.4释放锁时的公平锁与非公平锁

  • 当第一线程释放锁,这个时候有新线程进来的时候,是可能和第二个线程竞争一下锁的。这种是非公平锁,也就是默认的实现,线程可能会出现“饥饿”现象,即某些线程长时间无法获取到锁。公平锁是直接插到队尾。

2.5 Node中包含以下内容

-   locked:一个布尔类型的字段,用于表示当前节点是否被锁定。如果locked为true,表示当前节点已被锁定,其他线程需要等待;如果locked为false,表示当前节点未被锁定,可以被其他线程获取。
-   myPred:一个QNode类型的引用,表示当前节点的前驱节点。在获取锁的过程中,当前节点会尝试获取前驱节点的锁,如果前驱节点未被锁定,则当前节点可以继续尝试获取锁;如果前驱节点已被锁定,当前节点则需要等待前驱节点释放锁。
-   mySucc:一个QNode类型的引用,表示当前节点的后继节点。当前节点的后继节点会尝试获取当前节点的锁,如果当前节点未被锁定,则后继节点可以继续尝试获取锁;如果当前节点已被锁定,后继节点则需要等待当前节点释放锁。
-   waitStatus:一个整型字段,用于表示当前节点是否处于等待状态以及等待的原因。当一个线程在等待获取锁时,可以使用waitStatus来记录等待的原因,以便在获取到锁后执行相应的操作。
      SIGNAL状态(值为-1):头结点被前驱节点锁定,当前线程需要等待前驱节点释放锁。这种情况下,当前线程需要等待前驱节点释放锁后才能继续执行。
      CONDITION状态(值为0):头结点没有被锁定,但是存在等待该节点的线程。这种情况下,头结点需要等待线程唤醒后才能继续执行。
      PROPAGATE状态(值为-3):头结点没有被锁定,也没有等待该节点的线程。这种情况下,头结点可以立即执行。
-   exclusiveOwnerThread:一个Thread类型的字段,用于表示当前节点被哪个线程锁定。如果exclusiveOwnerThread为null,表示当前节点未被任何线程锁定;如果exclusiveOwnerThread不为null,表示当前节点已被exclusiveOwnerThread线程锁定。

3、图解

image.png

  • 核心思想就是,如果被请求的共享资源限闲置,就将当前请求资源的线程设置为有效工作线程,将共享资源状态设置为锁定状态。如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。主要靠队列来实现,将暂时加锁失败的线程加入到队列。

  • AQS支持独占锁(exclusive)和共享锁(share)两种模式。

  1. 独占锁:只能被一个线程获取到(Reentrantlock)。

  2. 共享锁:可以被多个线程同时获取(CountDownLatch,ReadWriteLock)。