Java多线程(四)——AQS

在之前的多线程分析中,我们反复的看到一个类,AbstractQueuedSynchronizer,AQS,或者可以叫做抽象的队列式同步器。根据我们之前的学习,对于它我们已经有了初步的认识,在这里,我们单独拿出来做一个梳理,这块东西搞明白了,多线程的很多实现类就明白了。

AQS是Java多线程的基础框架之一,ReentrantLock的sync、CountDownLatch的sync、ThreadPoolExcutor中的worker等等都是使用的AQS来实现的资源竞争。
实际上,AQS在内部用双向链表的形式维护了一个队列,这个队列中的节点都是volatile修饰的,整个队列可以认为是同步的。还维护了一个volatile int state的变量用来代表资源。当线程竞争state失败时,则进入到队列中的末尾,等待前一个节点释放资源时唤醒自己。它可以用下图表示:

获取锁

初始状态下state = 0,意味着锁是空闲的,当线程A试图获取锁时,state = 1,在线程A使用资源的过程中,如果新来一个线程B尝试获取锁,它发现state == 1,锁被占用,AQS会把线程B包装成一个node节点,放入到我们的同步队列的队尾,并且阻塞线程B(LockSupport.park())。

释放锁

当线程A释放锁时,会将state = 0,此时线程A会唤醒head节点的下一个节点(LockSupport.unpark(B)),B线程成功获取锁,然后继续上面的获取锁流程。

在ReentrantLock中,如果是同一个线程重复尝试获取锁,代码中我们不难发现,它其实是对state做了累加,这里state既是锁,也是锁被占有的次数,工作线程每释放一次锁,state减一,当state == 0的时候,表明当前线程所有持有该重入锁的地方都已经释放完毕。

CountDownLatch中,初始化的时候,直接对state = n,每当一个线程做了countdown操作后,state = state - 1,直到执行完n次countdown后,表明已经达到了预期的计数次数。

所以,我们不难看出,对于AQS特性的不用包装使用,就构成了java多线程中多种多样的并发工具类。