抽象队列同步器(Abstract Queued Synchronizer,AQS)作为并发包JUL中一个基础组件,用来实现各种锁和同步组件,AQS主要由状态state变量、加锁线程和等待队列组成。AQS定义了多线程访问共享资源的框架,AQS定义了Exclusive(Reentrantlock)和share(Semaphore和CountDownLacth)两种资源共享方式,在不同的组件中都有基于AQS的自定义同步器,不同的自定义同步器主要区别在获取state的方式不同。
AQS内通过一个FIFO队列来管理资源获取线程的排队工作,并通过一个int型的变量来表示线程持有锁的状态,通过CAS锁对int变量进行修改。int变量实现线程之间互斥,state等于1表示共享资源已经被线程加锁;state等于0,表示共享资源没有被人加锁;state大于1表示共享资源已经被同一个线程多次加锁。下图为AQS和其它类之间的一个集成依赖关系。
第一个过程是ReentranLock整体的加锁过程,这个过程主要描述了一个新任务的执行时机和新任务入队时机。
ReentrantLock的实现过程,在ReentrantLock通过集成AQS事项了公平了非公平的同步器,无参方法创建ReentrantLock时创建,内部调用的是非公平同步器。
public void lock() { //调用非公平阻塞的lock方法 sync.lock(); } final void lock() { //state初试值为0,表示第一个线程 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1);// 不是第一个线程 }
第二个过程是任务阻塞入队的过程,在真正阻塞之前,进入到任务等候队列,会两次尝试去获得资源锁,两次都获取不到,任务线程才会进入阻塞状态,等待前面的线程执行完毕。线程使用了LockSupport的自旋锁进行线程阻塞。
无法获得共享资源锁的线程会被封装在一个Node中,被阻塞后放在任务队列中等待被唤醒。Node中有一个表示线程状态的标志位waitStatus和记录阻塞线程的Thread变量。
Node中waitStatus的值为:
0:创建一个node的默认值。
CANCELLED(1):表示线程获取锁的请求已经被取消。
CONDITION(-2):表示节点在等待队列中,节点线程等待被唤醒。
PROPAGATE(-3):当先线程处于SHARED情况下,该字段才会被使用。
SIGNAL(-1):表示线程已经准备好了,等待资源被释放了。
最终任务队列中节点状态为:
1) 除了第一个节点,所有节点都处于阻塞状态;
2) 除了尾节点,所有节点waitStatus==Node.SIGNAL。
第三个过程是等待队列中的线程被唤醒的过程,主要描述了等待线程唤醒时机,唤醒线程从等待队列到执行所做的一些动作。
Node s = node.next;//node为head节点 if (s == null || s.waitStatus > 0) { s = null; //从尾部开始查找没有被取消的节点。 for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread);
因为Node节点在入队的时候,保证了prev的强一致性,node的next的是弱一致性。维护一致性通常需要牺牲部分性能,为了进一步的提升性能,使用了弱一致性。
node.prev = pred;//无条件执行 if (compareAndSetTail(pred, node)) { pred.next = node;//if满足才能执行 return node; }
AQS使用单向队列也能实现任务队列,为什么还要使用到双向队列,通过head指针和tail指针就可以方便节点的移除和增加,但是在很多场合,对当前节点的操作,需要知道前一个节点的状态,为了节省访问上一个节点的时间复杂度,使用了双向队列,查询当前节点上一个节点的时间复杂度为O(1)。
例如: final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor();//需要获取前驱节点信息 ... }
[1] https://www.bilibili.com/video/BV1Hy4y1B78T
[2]https://monkeysayhi.github.io/2017/12/05/%E6%BA%90%E7%A0%81%7C%E5%B9%B6%E5%8F%91%E4%B8%80%E6%9E%9D%E8%8A%B1%E4%B9%8BReentrantLock%E4%B8%8EAQS%EF%BC%881%EF%BC%89%EF%BC%9Alock%E3%80%81unlock/.