AQS即AbstractQueuedSynchronizer缩写,翻译为抽象队列同步器,平时使用较多的ReentrantLock、CountDownLatch就是基于AQS实现。
AQS只是一个框架,具体的资源获取/释放得由自定义的同步器去实现,同步器的设计是基于模板方法模式。因此AQS类中提供了一个重要的成员变量 state,用来给自定义同步器保存当前同步状态,并提供了对应的get/set方法来访问或修改同步状态。
/**
* 同步状态
*/
private volatile int state;
/**
* 返回同步状态的当前值
*/
protected final int getState() {
return state;
}
/**
* 设置同步状态的值
*/
protected final void setState(int newState) {
state = newState;
}
/**
* 如果当前状态值等于预期值,则原子地将同步状态设置为给定的更新值,CAS操作。
*/
protected final boolean compareAndSetState(int expect, int update) {
return U.compareAndSwapInt(this, STATE, expect, update);
}
资源的获取与释放在AQS中分为独占式与共享式,分别提供了如下方法:
方法名 | 描述 |
acquare() | 以独占模式获取同步状态,忽略中断。如果当前线程获取同步状态成功则返回。否则线程会排队,可能会反复阻塞和解除阻塞,调用 tryAcquire 直到成功 |
acquareInterruptibly() | 以独占模式获取,如果中断则中止。首先检查中断状态来实现,如果当前线程获取同步状态成功返回。否则线程会排队,可能会重复阻塞和解除阻塞,调用tryAcquire直到成功或线程被中断 |
tryAcquireNanos() | 以独占模式获取,如果中断则中止。在acquareInterruptibly基础上增加了超时限制 |
acquireShared() | 在共享模式下获取同步状态,忽略中断。如果当前线程未获取到同步状态,将会进入同步队列等待。与独占锁主要区别是同一时刻有多个线程获取到同步状态 |
acquireSharedInterruptibly() | 在共享模式下获取,如果中断则中止。其他与acquireShared相同 |
tryAcquireSharedNanos() | 在共享模式下获取,如果被中断则中止。在acquireSharedInterruptibly基础上增加了超时限制 |
AQS内部使用了CLH队列,是CLH队列锁的一种变体实现。CLH队列锁即Craig、Landin、Hagersten Locks,三个人名字。CLH队列锁是一种基于链表的自旋锁。
+---------+ prev +---------+ +---------+
head | P | L | <---- | P | L | <---- | P | L | tail
+---------+ +---------+ +---------+
A B C
只有队列首个线程A能取到锁,后续线程以链表形式排列,其中有一个P(myPred)节点用来指向前一个元素,一个L(locked)节点表示当前线程是否需要获取锁。当B线程需要获得锁,就在A线程的locded字段上做旋转,直到A释放锁(即前一个locked=false),这时B就可以获取到锁,同时回收前驱结点A。
CountDownLatch使用
假设有A、B、C三个线程,其中A、B线程执行分为三步,C线程需要在A、B都执行到第二步才能执行,怎么实现?
final CountDownLatch countDownLatch =new CountDownLatch(2);
Thread thread_A = new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG,"thread_A第一步执行");
Log.d(TAG,"thread_A第二步执行");
countDownLatch.countDown();
Log.d(TAG,"thread_A第三步执行");
}
});
Thread thread_B = new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG,"thread_B第一步执行");
Log.d(TAG,"thread_B第二步执行");
countDownLatch.countDown();
Log.d(TAG,"thread_B第三步执行");
}
});
Thread thread_C = new Thread(new Runnable() {
@Override
public void run() {
try {
//等待线程A、B执行完毕
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG,"thread_C执行");
}
});
thread_C.start();
thread_B.start();
thread_A.start();
当调用await方法,若状态值为0则不会发生阻塞,否则发生阻塞。调用countDown方法后,会利用CAS机制将状态值-1,直到状态值为0时,await将不再阻塞。