AQS原理
AQS(AbstractQueuedSynchronizer)抽象同步队列。是除了java自带的synchronized关键字之外的锁机制。它是实现同步器的基础组件,并发包中锁的底层就是使用AQS实现的。
AQS类图如下:
由该图可以看到,AQS是一个FIFO的双向队列,其内部通过节点head和tail记录队首和队尾元素,队列元素类型为Node。其中Node中的thread变量用来存放进入AQS队列里面的线程;Node节点内部的SHARED用来标记该线程是获取共享资源时被阻塞挂起后放入AQS队列的,EXCLUSIVE用来标记该线程是获取独占资源时被挂起后放入AQS队列的;waitStatus记录当前线程等待状态,可以为CANCELLED(线程被取消了)、SIGNAL(线程需要被唤醒)、CONDITION(线程在条件队列里面等待)、PROPAGATE(释放共享资源时需要通知其他节点);prev记录当前节点的前驱节点,next记录当前节点的后继节点。
AQS的核心思想:
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。
AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个节点(Node),来实现锁的分配。
简单来说,AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变装态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。
AQS是自旋锁,再等待被环形的时候,经常会使用自旋的方式,不停地尝试获取锁,直到被其他线程获取成功。
AQS-条件变量的支持
notify和wait,是配合synchronized内置锁实现线程间同步的基础设施一样,条件变量的signal和await方法也是用来配合锁(使用AQS实现的锁)实现线程间同步的基础设施。
它们的不同在于,synchronized同时只能与一个共享变量的notify或wait方法实现同步,而AQS的一个锁可以对应多个条件变量。
在调用共享变量的notify和wait方法前必须先获取该共享变量的内置锁,同理,在调用条件变量的signal和await方法前也必须先获取条件变量对应的锁。
总结如下:一个锁对应一个AQS阻塞队列,对应多个条件变量,每个条件变量有自己的一个条件队列。
基于AQS实现自定义同步器:
// 基于AQS实现不可重入的独占锁
class NonReentrantLock implements Lock, java.io.Serializable {
// 内部帮助类
private static class Sync extends AbstractQueuedSynchronizer {
// 是否已经被持有
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 如果sate为0 则尝试获取锁
public boolean tryAcquire(int acquires) {
assert acquires == 1;
if (compareAndSetState(0,1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 尝试释放锁,设置state为0
protected boolean tryRelease(int releases) {
assert releases == 1;
if (getState() == 0) {
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 提供条件变量接口
Condition newCondition() {
return new ConditionObject();
}
}
// 创建一个Sync来做具体的工作
private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
public boolean isLocked() {
return sync.isHeldExclusively();
}
}
- NonReentrantLock定义了一个内部类Sync用来实现具体的锁的操作,Sync则继承了AQS。由于我们实现的是独占模式的锁,所以Sync重写了tryAcquire、tryRelease和isHeldExclusively3个方法。另外,Sync提供了newCondition这个方法用来支持条件变量
使用自定义锁实现生产-消费模型:
public class ProducerTest {
final static NonReentrantLock lock = new NonReentrantLock();
// 创建两个变量,进行生产者和消费者之间的同步
final static Condition notFull = lock.newCondition();
final static Condition notEmpty = lock.newCondition();
final static Queue<String> queue = new LinkedBlockingQueue<String>();
final static int queueSize = 10;
public static void main(String[] args) {
Thread producer = new Thread(new Runnable() {
@Override
public void run() {
// 获取独占锁
lock.lock();
try {
// 如果队列满了,则等待
// 使用while而不是if是为了防止虚假唤醒
while (queue.size() == queueSize) {
notEmpty.await();
}
// 添加元素到队列
queue.add("ele");
System.out.println("生产一个元素");
// 唤醒消费线程
// 唤醒所有因为消费元素而被阻塞的消费线程
notFull.signalAll();
}catch (Exception e) {
e.printStackTrace();
}finally {
// 释放锁
lock.unlock();
}
}
});
Thread consumer = new Thread(new Runnable() {
@Override
public void run() {
// 获取独占锁
lock.lock();
try {
// 队列空,则等待
while(0 == queue.size()) {
notFull.await();
}
// 消费一个元素
String ele = queue.poll();
System.out.println("消费一个元素");
// 唤醒生产线程
notEmpty.signalAll();
}catch (Exception e) {
e.printStackTrace();
}finally {
// 释放锁
lock.unlock();
}
}
});
// 启动线程
producer.start();
consumer.start();
}
}
- 如上代码首先创建了NonReentrantLock的一个对象lock,然后调用了lock.newCodition创建了两个条件变量,用来进行生产者和消费者线程之间的同步。