AQS基本介绍

AQS(AbstractQueuedSynchronizer)是抽象队列同步器。它定义了多线程访问共享资源的同步框架,依赖队列同步器状态线程队列,实现资源的同步访问。诸如ReentrantLock、CountDownLatch、Semaphore等的实现都依赖了AQS。AQS支持设置成独占模式、共享模式或者两种模式兼而有之。

如果要定义一个队列同步器,需要重新定义下面的方法:

  1. //互斥模式
  2. tryAcquire()
  3. tryRelease()
  4. //共享模式
  5. tryAcquireShared()
  6. tryReleaseShared()
  7. isHeldExclusively()

可以通过下面的方法查看、修改同步状态。

  • getState();
  • setState();
  • compareAndSetState

首先看一下AQS定义的变量:

  1. //等待队列的头
  2. private transient volatile Node head;
  3. //等待队列的尾
  4. private transient volatile Node tail;
  5. //同步状态
  6. private volatile int state;
  7. //当前占有锁的线程
  8. private transient Thread exclusiveOwnerThread;

ReentrantLock

公平锁

基本流程

ReentrantLock的公平锁为例,state初始状态为0,当调用ReentrantLock的lock方法时。首先会调用AQS的acquire()方法。它会以独占的模式获取锁,如果成功,返回true,否则加入等待队列。

  1. static final class FairSync extends Sync {
  2. private static final long serialVersionUID = -3000897897090466540L;
  3. final void lock() {
  4. //1.调用AQS的acquire方法
  5. acquire(1);
  6. }
  7. /**
  8. * Fair version of tryAcquire. Don't grant access unless
  9. * recursive call or no waiters or is first.
  10. */
  11. protected final boolean tryAcquire(int acquires) {
  12. //1 获取当前线程和同步状态
  13. final Thread current = Thread.currentThread();
  14. int c = getState();
  15. //2 如果状态为0
  16. if (c == 0) {
  17. //如果没有没有前继节点且设置状态成功,将当前线程设置成拥有排它锁的线程。此处判断是否有前继节点,是因为公平锁要按照FIFO的规则去获取锁。
  18. if (!hasQueuedPredecessors() &&
  19. compareAndSetState(0, acquires)) {
  20. setExclusiveOwnerThread(current);
  21. return true;
  22. }
  23. }
  24. //如果当前当前线程拥有排它锁,可重入,状态位+1
  25. else if (current == getExclusiveOwnerThread()) {
  26. int nextc = c + acquires;
  27. if (nextc < 0)
  28. throw new Error("Maximum lock count exceeded");
  29. setState(nextc);
  30. return true;
  31. }
  32. return false;
  33. }
  34. }
  1. //2.如果获取资源成功,返回true;否则的话,加入到队列当中
  2. public final void acquire(int arg) {
  3. if (!tryAcquire(arg) &&
  4. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  5. selfInterrupt();
  6. }

队列

加锁如果不存在竞争,那么当前线程可以获取到锁,此时不需要排队。当有多个线程竞争锁时,此时需要排队。排队分为两步操作:

  • 调用addWaiter(Node.EXCLUSIVE), arg)方法,创建队列节点并加入到队列当中,等待队列的数据结构如下:

  • 调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法

当前线程加入队列后,此时获取到锁的线程有可能恰好释放锁。此处会再次尝试获取锁,没有获取到锁的话,会将上一个节点的waitStatus从0改成-1,表示上一个节点处于睡眠状态。

  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();
  7. //判断上一个节点是否是head节点,如果是的话,尝试获取锁。获取到锁的话,
  8. if (p == head && tryAcquire(arg)) {
  9. setHead(node);
  10. p.next = null; // help GC
  11. failed = false;
  12. return interrupted;
  13. }
  14. //将上一个节点的waitStatus设置成阻塞状态,然后将自己park
  15. if (shouldParkAfterFailedAcquire(p, node) &&
  16. parkAndCheckInterrupt())
  17. interrupted = true;
  18. }
  19. } finally {
  20. if (failed)
  21. cancelAcquire(node);
  22. }
  23. }
  1. private final boolean parkAndCheckInterrupt() {
  2. LockSupport.park(this);//当前线程阻塞在这里,等待被唤醒。
  3. return Thread.interrupted();
  4. }

锁释放

当执行完锁释放后,如果队列中有等待的线程,那么唤醒该线程。唤醒后会继续继续自悬,尝试获取锁。如果获取到锁之后,将当前节点设置成头节点。也就是下面的setHead方法。

  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();
  7. if (p == head && tryAcquire(arg)) {
  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. }
  1. private void setHead(Node node) {
  2. head = node;
  3. node.thread = null;
  4. node.prev = null;
  5. }

https://www.cnblogs.com/waterystone/p/4920797.html