AQS原理及应用
To use this class as the basis of a synchronizer, redefine the * following methods, as applicable, by inspecting and/or modifying * the synchronization state using {@link #getState}, {@link * #setState} and/or {@link #compareAndSetState}: * * <ul> * <li> {@link #tryAcquire} * <li> {@link #tryRelease} * <li> {@link #tryAcquireShared} * <li> {@link #tryReleaseShared} * <li> {@link #isHeldExclusively} * </ul>
上面这段话是AQS源码的一段注解,意思是使用AQS实现一个同步器的话需要覆盖实现上面li标签中的这些方法,并且使用getState、setState、compareAndSetState这几个方法来对状态进行操作。
如果你对JDK源码较为熟悉的话,你会发现AQS(AbstractQueuedSynchronizer)是并发过程中很常见的一个抽象类,我们常用的CountDownLatch、ReentrantLock、FutureTask(1.8不再使用AQS)、Semaphore等类都是在内部定义了一个叫做Sync的内部类,而Sync类继承了AQS抽象类并重写了一些必要的方法。可重入锁ReentrantLock中,state可以用来表示当前线程获取锁的可重入次数;对于读写锁ReentrantReadWriteLock来说,state的高16位表示获取到的读锁的线程的可重入次数,低16位是写锁;对于Semaphore来说,state用来表示当前可用信号的个数;对于CountDownlatch来说,state用来表示计数器当前的值
abstract static class Sync extends AbstractQueuedSynchronizer { ... ... ... }
下面说说AQS到底是用来做什么的:
AQS抽象类的内部是一个final的Node类,类中定义了一个volatile的整形状态量state,通过这个Node类来建立一个FIFO的线程等待队列(多线程抢占资源失败被阻塞时会进入此队列)。
static final class Node { /** Marker to indicate a node is waiting in shared mode */ static final Node SHARED = new Node(); /** Marker to indicate a node is waiting in exclusive mode */ static final Node EXCLUSIVE = null; /** waitStatus value to indicate thread has cancelled */ static final int CANCELLED = 1; /** waitStatus value to indicate successor's thread needs unparking */ static final int SIGNAL = -1; /** waitStatus value to indicate thread is waiting on condition */ static final int CONDITION = -2; /** * waitStatus value to indicate the next acquireShared should * unconditionally propagate */ static final int PROPAGATE = -3;
}
而这个队列中的每个结点对应一个线程,结点内封装了这个线程的基本信息,状态和等待的资源类型等。
* <p>To enqueue into a CLH lock, you atomically splice it in as new * tail. To dequeue, you just set the head field. * <pre> * +------+ prev +-----+ +-----+ * head | | <---- | | <---- | | tail * +------+ +-----+ +-----+ * </pre> * * <p>Insertion into a CLH queue requires only a single atomic * operation on "tail", so there is a simple atomic point of * demarcation from unqueued to queued. Similarly, dequeuing * involves only updating the "head". However, it takes a bit * more work for nodes to determine who their successors are, * in part to deal with possible cancellation due to timeouts * and interrupts.
这里借用网上的一张图来说明这个队列的框架。
AQS定义了两种资源共享的方式,独占和共享,Exclusive和Share,前者常用的是ReentrantLock,后者常用的是前面提到的CountDownLatch、Semaphore等。
继承AQS,实现state标志位的获取、释放方式,即可实现自定义同步器,至于具体的等待队列的维护、阻塞入队、唤醒出队等在AQS中已经实现好了。标志位涉及到的实现函数如下:
-
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
-
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
-
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
-
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
-
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
自定义同步器
独占锁以ReentrantLock为例,state初始值为0,表示未锁状态,当一个线程执行了lock()方法之后,会调用tryAcquire方法独占该锁并将state+1,此后其他线程尝试获取锁时便会失败,进入队列,知道刚才的线程释放锁将state值更改为0时,其他线程才有机会获取这个独占锁。当然,这个锁时可重入的,获得锁的线程可以继续state+1,不过只有state为0的时候其他线程才会唤醒。
共享锁以CountDownLatch为例,初始化线程数为n,及将state初始值设置为n,表示可以有n个线程同时获取这个共享锁,当有线程执行一countDown(),state就会CAS的方式减少1,知道所有线程执行完,state值为0的时候,会unpark主线程,然后主线程会从await状态被唤醒返回,继续执行其他指令。
一般情况自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。需要说明的是AQS也支持同时实现独占同步和共享同步,比如ReentrantReadWriteLock。
通常,如果一个线程没有拿到锁,便会被封装成结点加入队尾,检查状态之后会调用park方法进入waiting状态,等待unpark方法或interrupt方法唤醒自己,当它被唤醒之后会检查自己是否有资格拿到锁,如果成功,head会指向当前节点,并检查是否被中断过;否则会继续等待。
AQS是支持中断的,比如acquireInterruptibly方法和acquireSharedInterruptibly方法。下面将书上看到的互斥锁Mutex源码贴在下面供大家研究,它不支持重入,只有0(未锁定)和1(锁定)两种状态:
class Mutex implements Lock, java.io.Serializable { // 自定义同步器 private static class Sync extends AbstractQueuedSynchronizer { // 判断是否锁定状态 protected boolean isHeldExclusively() { return getState() == 1; } // 尝试获取资源,立即返回。成功则返回true,否则false。 public boolean tryAcquire(int acquires) { assert acquires == 1; // 这里限定只能为1个量 if (compareAndSetState(0, 1)) {//state为0才设置为1,不可重入! setExclusiveOwnerThread(Thread.currentThread());//设置为当前线程独占资源 return true; } return false; } // 尝试释放资源,立即返回。成功则为true,否则false。 protected boolean tryRelease(int releases) { assert releases == 1; // 限定为1个量 if (getState() == 0)//既然来释放,那肯定就是已占有状态了。只是为了保险,多层判断! throw new IllegalMonitorStateException(); setExclusiveOwnerThread(null); setState(0);//释放资源,放弃占有状态 return true; } } // 真正同步类的实现都依赖继承于AQS的自定义同步器! private final Sync sync = new Sync(); //lock<-->acquire。两者语义一样:获取资源,即便等待,直到成功才返回。 public void lock() { sync.acquire(1); } //tryLock<-->tryAcquire。两者语义一样:尝试获取资源,要求立即返回。成功则为true,失败则为false。 public boolean tryLock() { return sync.tryAcquire(1); } //unlock<-->release。两者语文一样:释放资源。 public void unlock() { sync.release(1); } //锁是否占有状态 public boolean isLocked() { return sync.isHeldExclusively(); } }