AQS和其衍生并发工具剖析

380 阅读7分钟

AQS和其衍生并发工具剖析

总览AQS

AQS : 是Java1.5提供的一个用来提供同步服务的框架,内部通过使用一个FIFO的CLH队列来进行多线程的互斥/协同操作,大体框架分为两种大模式EXCLUSIVE独占模式、SHARED共享模式,通过定义好模板方法,以及核心的同步/唤醒规则。方便我们自定义扩展同步并发工具。其中并发包中的ReentrantLockSemaphoreCountDownLatch等,都是AQS扩展实现

框架流程

基本数据结构

  • FIFO的双端队列
  • volatile state变量来存储状态信息 : 可以是重入锁数量、Permits数量等等。

核心API

只是列举常见的重要方法,可暂时略过,看到具体方法流程解析后,再看api。

核心模板方法

实际使用,可以直接调用模板方法进行资源(state)修改

  • acquire() : 独享模式获取,与之对应的还有acquireInterrutibly()支持中断的获取
  • release() : 独享模式释放
  • acquireShared : 共享模式的获取 , 也有支持中断的版本acquireSharedInterrutibly
  • releaseShared : 共享模式释放
子类扩展方法
  • tryXXX,其中XXX可为Acquire/Release等。都有对应共享模式和独享模式以及获取和释放模式。为子类实现,为具体的获取/释放规则。
  • doXXXX,XXX可为acquire/release/acquireShared.., 是AQS中固定实现的获取失败/释放成功的回调方法,为核心的等待/唤醒逻辑
相关辅助方法
  • addWaite() : 当获取锁失败的时候,添加到等待队列末尾逻辑
  • shouldParkAfterFaildAcquire() : 核心判断是否需要等待逻辑 , 会将Node节点的waitStatus设置为SIGNAL,然后循环判断/等待
  • parkAndCheckInterrupt : 调用LockSupport#part()进行等待
  • hasQueuedPredecessors : 是否有线程正在等待
  • cancelAcquire :取消获取
  • acquireQueued : 核心等待逻辑

独享核心流程源码解析

acquire() 资源获取方法
public final void acquire(int arg) {
  /**
         * 1. tryAcquire() 独占模式获取锁,忽略中断
         *  - FairLock : 若无线程等待或自己重入则尝试获取锁
         *  - NonfairLock: 无线程获取锁/自己重入则重试获取锁
         * 2. 获取锁失败,则被添加到等待队列末尾,并重新进行尝试获取
         *  - addWaiter() 添加到等待队列末尾,head节点为虚节点不存储数据
         *  - acquireQueued() 自旋/等待获取锁
         */
  if (!tryAcquire(arg) &&
      acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    selfInterrupt();/*若中断了,则重新设置中断标记*/
}
tryAcquire() : 子类实现,具体获取资源方法,下面用ReentrantLock举例
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) { /*1. 若无线程获取锁,则判断是否有线程在等待获取锁(有没有则CAS尝试获取锁)*/
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } /*2. 若当前线程占锁则累加次数(重入)*/
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

比较简单 , 大体分两个流程

  1. c==0,表示没有线程占锁,则判断是否有节点等待hasQueuedPredecessors(),这是公平锁的实现,而NonfairLock会直接CAS尝试获取锁
	//这里写一大堆,其实就是判断 离head节点最近的 waitStatus<=0的节点是否为当前线程持有
	//若不是则说明有线程在等待获取锁
	public final boolean hasQueuedPredecessors() {
        Node h, s;
        if ((h = head) != null) {
            if ((s = h.next) == null || s.waitStatus > 0) {
                s = null; // traverse in case of concurrent cancellation
                for (Node p = tail; p != h && p != null; p = p.prev) {
                    if (p.waitStatus <= 0)
                        s = p;
                }
            }
            if (s != null && s.thread != Thread.currentThread())
                return true;
        }
        return false;
    }
  1. currentThread == getExclusiveOwnerThread : 表示为重入锁,state+=acquire即可
锁获取失败 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
  • addWaiter() : 构造节点,并将其添加到末尾,若为初始化则初始化head节点
    private Node addWaiter(Node mode) {
        Node node = new Node(mode);
        for (;;) {
            Node oldTail = tail;
            if (oldTail != null) {
              	//cas设置为tail节点
                node.setPrevRelaxed(oldTail);
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return node;
                }
            } else {
              	//初始化head节点
                initializeSyncQueue();
            }
        }
    }
  • acquireQueued() : 自旋获取锁/等待,并返回是否中断标志
    final boolean acquireQueued(final Node node, int arg) {
        boolean interrupted = false;
        try {
            for (;;) {
                final Node p = node.predecessor(); /*1. 获取当前节点的前置节点*/
                if (p == head && tryAcquire(arg)) { /*2. 判断是否是prev是否是头节点,是则尝试获取锁*/
                    setHead(node); /*3. 获取成功则设置为头结点并返回中断标记*/
                    p.next = null; // help GC
                    return interrupted;
                } /*4. prev是头结点,但被其他锁抢占了(NonfairLock) 或者 prev不是头结点 若prev节点状态不是SIGNAL则设置为SIGNAL并返回false,其中会删除所有的CANCEL节点,若是SIGNAL则返回true*/
                if (shouldParkAfterFailedAcquire(p, node))
                    interrupted |= parkAndCheckInterrupt();/*5. 对SIGNAL进行等待唤醒获取锁*/
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            if (interrupted)
                selfInterrupt();
            throw t;
        }
    }
release()释放资源方法
  1. 尝试释放锁,由子类实现
  2. 若释放锁成功,则将head.next唤醒 (唤醒下一个节点)
    public final boolean release(int arg) {
      /*1. 释放锁,若是当前线程,则减去对应state,state到0则释放锁成功*/
        if (tryRelease(arg)) {
            Node h = head;
          /*这里h==null说明没有等待线程,h.waitStatus==0 只有当其他线程获锁失败,且acquireQueue()还没有进行等待时 (不需要唤醒)*/
            if (h != null && h.waitStatus != 0) 
                unparkSuccessor(h); /*2. 唤醒next线程*/
            return true;
        }
        return false;
    }
ReentrantLock#tryRelease()实现
			//就是减去release,判断判断是否为0,为0则可以方式锁,否则返回false
			protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
unparkSuccessor() : 唤醒 node.next节点锁持有的线程,若状态非法,比如为null , 或者已经取消

这里从如果node.next 的waitStatus > 0 ,表示已经被取消,则从tail -> node 依次向前查找到最靠近node的合理唤醒节点进行unpark()

private void unparkSuccessor(Node node) {
				 /*将当前节点waitStatus设置为0*/
        int ws = node.waitStatus;
        if (ws < 0)
            node.compareAndSetWaitStatus(ws, 0);
				/*为什么都是从tail向前遍历?因为addWaiter是非原子操作,极端情况可以只有prev指针相连*/
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null; 
            for (Node p = tail; p != node && p != null; p = p.prev)
                if (p.waitStatus <= 0)
                    s = p;
        }
        if (s != null) /*获取队列最前方的节点进行唤醒*/
            LockSupport.unpark(s.thread);
    }
await() : 为Condition的具体等待实现

其实个人认为AQS核心思想可以类比管程,管程也有多个条件变量,且每个变量有自己的等待队列,await()的时候加入等待队列并释放锁,signal的时候会将条件变量的firstWaite节点添加到同步队列(AQS的等待队列),unlock()的时候会唤醒同步队列中head.next线程,从而完成等待唤醒工作

        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
          	//1. 添加节点到CondtionObject#lastWriter
            Node node = addConditionWaiter();
          	//2. 释放锁并唤醒其他线程
            int savedState = fullyRelease(node);
            int interruptMode = 0
						 //3. 当不在同步队列中(第一次肯定不在)则进行阻塞
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
          	//4. 解锁之后尝试获取锁
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
signal

将当前ConditionObject#firstWaiter添加到AQS同步队列中 , 使用enq(),并在解锁unlock()时唤醒

        public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
          	//1. 获取firstWaiter
            Node first = firstWaiter;
            if (first != null)
							//2. doSignal
                doSignal(first);
        }

        private void doSignal(Node first) {
          	//将firstWaiter出队列
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
              	//help GC
                first.nextWaiter = null;
              //传送到同步队列中
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

    final boolean transferForSignal(Node node) {
      	//若CAS修改状态失败则返回false
        if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
            return false;
				// 重点 : 加入到同步队列末尾,并返回tail节点
        Node p = enq(node);
      	//若tail节点被取消,或者CAS设置为SIGNAL失败,则直接唤醒,否则,则等到unlock()的时候唤醒
        int ws = p.waitStatus;
        if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

共享核心流程源码解析

acquireShared()
    public final void acquireShared(int arg) {
      	//1. 子类实现,获取permits,返回值有三种值类型
      	//   正值表示获取permits成功,并且还有剩余的permits
      	//   为0则表示获取成功,但是已经没有剩余的permits了
				//   为负值表示 获取失败。
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

Semaphore#tryAcquireShared() : 信号量公平锁实现

protected int tryAcquireShared(int acquires) {
  for (;;) {
    //若同步队列中有节点正在等待则返回-1,表示获取失败
    if (hasQueuedPredecessors())
      return -1;
    int available = getState();
    int remaining = available - acquires;
    //返回剩余的permits,若剩余的不小于0则CAS修改
    if (remaining < 0 ||
        compareAndSetState(available, remaining))
      return remaining;
  }
}

doAcquireShared() : 获取共享锁permits失败的操作

private void doAcquireShared(int arg) {
  //1. 添加节点到tail
  final Node node = addWaiter(Node.SHARED);
  boolean interrupted = false;
  try {
    for (;;) {
      //2. 和独占锁差不多的流程,都是判断prev是否是head,然后尝试获取permits
      final Node p = node.predecessor();
      if (p == head) {
        int r = tryAcquireShared(arg);
        //3. 这里有锁不同,这里当获取的permits>=0时,也就是获取permits成功
        if (r >= 0) {
          //4. 将当前节点设置为head,当r >0 时还会唤醒node.next节点
          setHeadAndPropagate(node, r);
          p.next = null; // help GC
          return;
        }
      }
      //5. 若不是head 或者 获取锁失败则进行阻塞
      if (shouldParkAfterFailedAcquire(p, node))
        interrupted |= parkAndCheckInterrupt();
    }
  } catch (Throwable t) {
    //6. 异常则取消当前获取操作并抛出异常
    cancelAcquire(node);
    throw t;
  } finally {
    //7. 若中断,则重新设置中断标志
    if (interrupted)
      selfInterrupt();
  }
}

总结:其实无论是独占还是共享,都是以下几个步骤

  1. 通过子类实现的tryXXx进行相关锁(permits或者其他关于state属性的操作)获取
  2. 获取失败则添加到阻塞队列,并进行等待
  3. release的时候,会通过unparkSuccessor来唤醒

核心组件实现

ReentrantLock

其中实现为 独占模式,其中state代表重入的次数。通过AQSacquire来完成互斥锁

//tryAcquire实现,其实就是简单的CAS修改state变量
protected final boolean tryAcquire(int acquires) {
  final Thread current = Thread.currentThread();
  int c = getState();
  if (c == 0) {
    //1. 公平锁判断同步队列中是否有节点正在等待,没有则尝试CAS获取锁
    if (!hasQueuedPredecessors() &&
        compareAndSetState(0, acquires)) {
      setExclusiveOwnerThread(current);
      return true;
    }
  }
	//2. 重入逻辑,累加state即可
  else if (current == getExclusiveOwnerThread()) {
    int nextc = c + acquires;
    if (nextc < 0)
      throw new Error("Maximum lock count exceeded");
    setState(nextc);
    return true;
  }
  return false;
}
//tryRelease,若state减少到0,则释放锁。若不为0则释放失败
protected final boolean tryRelease(int releases) {
  int c = getState() - releases;
  if (Thread.currentThread() != getExclusiveOwnerThread())
    throw new IllegalMonitorStateException();
  boolean free = false;
  if (c == 0) {
    free = true;
    setExclusiveOwnerThread(null);
  }
  setState(c);
  return free;
}

Semaphore

信号量是允许多个线程进入临界区的工具,所以必须是共享模式实现

//tryAcquireShared
protected int tryAcquireShared(int acquires) {
  for (;;) {
    //公平锁实现,同步队列有节点等待则直接返回失败-1
    if (hasQueuedPredecessors())
      return -1;
    int available = getState();
    int remaining = available - acquires;
    if (remaining < 0 ||
        compareAndSetState(available, remaining))
      //若remaining >=0则进行CAS设置,否则直接返回remaining
      return remaining;
  }
}
//tryReleaseShared,同样是CAS设置state就算成功
protected final boolean tryReleaseShared(int releases) {
  for (;;) {
    int current = getState();
    int next = current + releases;
    if (next < current) // overflow
      throw new Error("Maximum permit count exceeded");
    if (compareAndSetState(current, next))
      return true;
  }
}

CountDownLatch

共享模式实现,用来在一个或多个线程中等待其他线程完成

其中初始化的时候,给state设置了一定值 countDown()方法则是通过复写tryReleaseShared来进行state的释放,当减到0的时候,进行唤醒 await()方法则是用过tryAcquireShared能力来进行阻塞

//扩展AQS
private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }
				//1. await()方法,判断state值不为0即返回-1,会进入doAcquireShared进行等待
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
				//2. countDown()方法会减少state的值,当state=0的时候,会释放锁同时唤醒等待线程
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c - 1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

CyclicBarrier

这个工具翻译为循环篱笆,是CountDownLatch的增强,一般使用场景在于等待多个线程执行完毕然后完成一定逻辑。且可以自动reset计数。底层是通过ReentrantLock + Condition来完成,核心实现为await()

    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock(); //1. 直接加锁
        try {
            final Generation g = generation;

            if (g.broken)
                throw new BrokenBarrierException();
					
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
							//2. 计数为0,则执行传入的Runnable,并重置计数
            int index = --count;
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                  	//2.1 重置计数并signalAll()唤醒所有等待线程
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }
							//3. 计数不为0,则进行Condition等待
            for (;;) {
                try {
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        Thread.currentThread().interrupt();
                    }
                }
									//相关异常和超时处理
                if (g.broken)
                    throw new BrokenBarrierException();

                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

ReentrantReadWriterLock

读写锁,其中state的高16位读锁次数,低16位写锁次数。写锁为独占模式,读锁为共享模式

读读不加锁,读写加锁,写写加锁,具体实现类似于ReentrantLock,只是区分不同读写锁状态