它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。这里volatile是核心关键词,具体volatile的语义,在此不述。state的访问方式有三种:
- getState()
- setState()
- compareAndSetState()
prev和next指针主要是中断和唤醒后续阻塞线程时需要用到
AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:
- isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
- tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
- tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
- tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
- tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
Node
Node结点是对每一个等待获取资源的线程的封装,其包含了需要同步的线程本身及其等待状态,如是否被阻塞、是否等待唤醒、是否已经被取消等。变量waitStatus则表示当前Node结点的等待状态,共有5种取值CANCELLED、SIGNAL、CONDITION、PROPAGATE、0。
- CANCELLED(1):表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。
- SIGNAL(-1):表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。
- CONDITION(-2):表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
- PROPAGATE(-3):共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。
- 0:新结点入队时的默认状态。
注意,负值表示结点处于有效等待状态,而正值表示结点已被取消。所以源码中很多地方用>0、<0来判断结点的状态是否正常。
acquire(int)
函数流程
- 调用自定义同步器的tryAcquire()尝试直接去获取资源,如果成功则直接返回;
- 没成功,则addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
- acquireQueued()使线程在等待队列中休息,有机会时(轮到自己,会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
- 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。
1.1 tryAcquire(int)
1.2 addWaiter(Node)
1.2.1 enq(Node)
1.3 acquireQueued(Node, int)
1.3.1 shouldParkAfterFailedAcquire(Node, Node)
1.3.2 parkAndCheckInterrupt()
park()会让当前线程进入waiting状态。在此状态下,有两种途径可以唤醒该线程:1)被unpark();2)被interrupt()。
Thread.interrupted()会清除当前线程的中断标记位。
acquire()
- 调用自定义同步器的tryAcquire()尝试直接去获取资源,如果成功则直接返回;
- 没成功,则addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
- acquireQueued()使线程在等待队列中休息,有机会时(轮到自己,会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
- 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。
LockSupport
关于AQS与synchronized关键字之间的关系
1. synchronized关键字在底层的C++实现中,存在两个重要的数据结构(集合):WaitSet, EntryList
2. WaitSet中存放的是调用了Object的wait方法的线程对象(被封装成了C++的Node对象)
3. EntryList中存放的是陷入到阻塞状态、需要获取monitor的那些线程对象
4. 当一个线程被notify后,它就会从WaitSet中移动到EntryList中。
5. 进入到EntryList后,该线程依然需要与其他线程争抢monitor对象
6. 如果争抢到,就表示该线程获取到了对象的锁,它就可以以排他方式执行对应的同步代码。
- AQS中存在两种队列,分别是Condition对象上的条件队列,以及AQS本身的阻塞队列
2. 这两个队列中的每一个对象都是Node实例(里面封装了线程对象)
3. 当位于Condition条件队列中的线程被其他线程signal后,该线程就会从条件队列中移动到AQS的阻塞队列中。
4. 位于AQS阻塞队列中的Node对象本质上都是由一个双向链表来构成的。
5. 在获取AQS锁时,这些进入到阻塞队列中的线程会按照在队列中的排序先后尝试获取。
6. 当AQS阻塞队列中的线程获取到锁后,就表示该线程已经可以正常执行了
7. 陷入到阻塞状态的线程,依然需要进入到操作系统的内核态,进入阻塞(park方法实现)