AQS的全称是AbstractQueuedSynchrizeder(抽象队列同步器)。它是为实现依赖于先进先出等待队列的阻塞锁和各种同步器(Semaphore,CountdownLatch,CyclicBarrier,Exchanger等等)而提供的一种基础框架。
它内部有一个int类型的state变量,被volatile关键字修饰,保证线程之间的可见。还会维护一个Node内部类(用于生成同步对列和等待队列),并继承过来一个加锁线程。state变量的访问方式有三种:getState(),setState(int),compareAndSetState(int,int)三个方法。AQS定义了两种资源共享的方式,独占模式和共享方式。
使用此类:
为了将此类用作同步器的基础,需要适当的重新定义以下方法,这是通过使用getState(),setState(int),compareAndSetState(int,int)三个方法来检查或修改同步状态来实现的。
tryAcquire(int) 试图在独占模式下获取对象状态,由acquire自动调用,至少调用一次
tryRelease(int) 试图设置状态来反映独占模式下的一个释放,由release自动调用,至少调用一次
tryAcquireShared(int) 试图在共享模式下获取对象状态,由acquireShared自动调用,至少调用一次
tryReleaseShared(int) 试图设置状态来反映共享模式下的一个释放,由releaseShared自动调用,至少调用一次
默认情况下,每个方法都抛出UnsupportedOperationException。这些方法的实现在内部必须是线程安全的,通常应该很短并且不被阻塞。
AQS的源码中涉及到的锁有独占锁与共享锁,独占锁就是只有一个线程可以获取锁,共享锁就是同时可以有多个线程获取锁。
代码层体现下AQS的state属性,为0,则锁没有被线程所持有,独占锁时为1,共享锁时大于0,代表获取此锁的现成的数量。
AQS的内部类Node定义了两个常量SHARED和EXCLUSIVE,他们分别标识AQS队列中等待线程的锁的获取模式。
/**
* The synchronization state.
*/
private volatile int state;
JUC中ReentrantLock与CyclicBarrier为独占锁,CountDownLatch与Semaphore为共享锁,
ReentrantReadWriteLock中writeLock为独占锁,ReadLock为共享锁。
独占锁与共享锁的区别:
- 独占功能
当锁被头节点获取后,只有头节点获取锁,其余节点的线程继续沉睡,等待锁被释放后,才会唤醒下一个节点的线程。 - 共享功能
只要头节点获取锁成功,就在唤醒自身节点对应的线程的同时,继续唤醒AQS队列中的下一个节点的线程,每个节点在唤醒自身的同时还会唤醒下一个节点对应的线程,以实现共享状态的“向后传播”,从而实现共享功能。
ReentrantLock就是使用AQS而实现的一把锁,它实现了可重入锁,公平锁和非公平锁。它有一个内部类用作同步器是Sync,Sync是继承了AQS的一个子类,并且公平锁和非公平锁是继承了Sync的两个子类。ReentrantLock的原理是:假设有一个线程A来尝试获取锁,它会先CAS修改state的值,从0修改到1,如果修改成功,那就说明获取锁成功,设置加锁线程为当前线程。如果此时又有一个线程B来尝试获取锁,那么它也会CAS修改state的值,从0修改到1,因为线程A已经修改了state的值,那么线程B就会修改失败,然后他会判断一下加锁线程是否为自己本身线程,如果是自己本身线程的话它就会将state的值直接加1,这是为了实现锁的可重入。如果加锁线程不是当前线程的话,那么就会将它生成一个Node节点,加入到等待队列的队尾,直到什么时候线程A释放了锁它会唤醒等待队列队头的线程。这里还要分为公平锁和非公平锁,默认为非公平锁,公平锁和非公平锁无非就差了一步。如果是公平锁,此时又有外来线程尝试获取锁,它会首先判断一下等待队列是否有第一个节点,如果有第一个节点,就说明等待队列不为空,有等待获取锁的线程,那么它就不会去同步队列中抢占cpu资源。如果是非公平锁的话,它就不会判断等待队列是否有第一个节点,它会直接前往同步对列中去抢占cpu资源。
以下是ReentrantLock的原理图解,简单明了: