1. AQS
AQS全称(AbstractOwnableSynchronizer),在包package java.util.concurrent.locks
下。
/**
* Provides a framework for implementing blocking locks and related
* synchronizers (semaphores, events, etc) that rely on
* first-in-first-out (FIFO) wait queues. This class is designed to
* be a useful basis for most kinds of synchronizers that rely on a
* single atomic {@code int} value to represent state.
*/
从源码中的注释可以看出 AQS是一个框架用来构建阻塞锁和基于等待队列的同步器,同时它使用了一个原子变量来代表状态。
查看继承结构可以看出,CountDownLatch,ReentrantLock…都是通过AQS实现的。
2. 原理
AQS的核心思想是:如果 被 请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将资源的状态设置为锁定状态。如果 被 请求的资源被占用,就需要线程阻塞等待以及被唤醒的 锁分配机制。
AQS使用CLH队列锁实现(自旋锁),将无法获取资源的线程加入到队列
CLH是一个虚拟的双向队列, 并不是真实存在的队列,只有一个个的节点,定义节点之间的关系来实现队列的特性。
CLH会把请求的线程封装成一个个的节点加入到队列中。
自旋锁:如果请求线程无法获取共享资源,自旋锁已经被别的线程持有了,线程不会进入休眠状态,而是循环等待 占有自旋锁的线程释放锁。
队列中的节点
static final class Node {
// 资源的占有方式,下边会讲到
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
// 表示节点的状态,节点取消调度
static final int CANCELLED = 1;
// 表示后继节点等待当前节点的唤醒
static final int SIGNAL = -1;
// 表示当前节点在condition上
static final int CONDITION = -2;
// 共享模式,会唤醒后继节点,以及后继节点的后继节点。
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {}
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
怎么判断资源是否被占用呢?
AQS使用一个 int 变量来表示同步: 使用 CAS 和 Volatile 来实现同步
// volatile 保证内存的可见性
private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
// CAS操作(将state设置为updata,前提是当前state的值为expect)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
资源的占有方式
AQS定义的两种资源的占有方式:
-
EXCLUSIVE (独占): 只有一个线程可以占有资源。比如: ReentrantLock 。
-
又可以分为公平锁和非公平锁:
-
公平锁: 按照线程在队列中的顺序先到先得
-
非公平锁:无视线程在队列中的顺序,谁抢到就是谁的
-
-
-
SHARE(共享): 多个线程可以占有资源。比如:Semaphore,CountDownLatch,CyclicBarrier,ReadWriteLock
AQS使用了模板模式
当我们需要创建自定义的同步器时只需要简单的步骤:
-
继承AQS类,并重写指定的方法。
自定义同步器重写下面这几个模板方法。
isHeldExclusively()//该线程是否正在独占资源。只有⽤到condition才需要去实现它。 tryAcquire(int)//独占⽅式。尝试获取资源,成功则返回true,失败则返回false。 tryRelease(int)//独占⽅式。尝试释放资源,成功则返回true,失败则返回false。 tryAcquireShared(int)//共享⽅式。尝试获取资源。负数表示失败; 0表示成功,但没有剩余可⽤资源;正数表示成功,且有剩余资源。 tryReleaseShared(int)//共享⽅式。尝试释放资源,成功则返回true,失败则返回 false。
一般来说,自定义同步器要么是独占式,要不是共享式,可以选择性的重写方法。
但是也支持同时实现独占式和共享式,如读写锁ReentrantReadWriteLock