AQS
什么是AQS?
是阻塞式锁和相关同步器工具的框架,如ReentrantLock内部就持有这个同步器,具体实现都是调用同步器的API实现加锁解锁
==========================ReentrantLock中的Sync================================
private final ReentrantLock.Sync sync;
public ReentrantLock() {//默认非公平
this.sync = new ReentrantLock.NonfairSync();
}
==========================Sync继承自AQS========================================
abstract static class Sync extends AbstractQueuedSynchronizer
AQS特点
AQS采用模板模式:tryXXX方法是需要子类需要实现的,而Acquire是AQS提供的,调用的是tryXXX
//获取锁
public final void acquire(int arg) {
if (!this.tryAcquire(arg) && this.acquireQueued(this.addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg)) {
selfInterrupt();
}
}
//AQS只抛出异常
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
AQS中的组件
主要有三个组件:State,ConditionObject,CHL队列
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements Serializable {
//CHL队列
private transient volatile AbstractQueuedSynchronizer.Node head;
private transient volatile AbstractQueuedSynchronizer.Node tail;
//资源状态位
private volatile int state;
static final long SPIN_FOR_TIMEOUT_THRESHOLD = 1000L;
//条件变量
public class ConditionObject implements Condition, Serializable {
private static final long serialVersionUID = 1173984872572414699L;
private transient AbstractQueuedSynchronizer.Node firstWaiter;
private transient AbstractQueuedSynchronizer.Node lastWaiter;
private static final int REINTERRUPT = 1;
private static final int THROW_IE = -1;
}
//............
}
state:共享资源状态
ConditionObject:条件变量,把对资源争用的线程划分为不同种类,可根据条件对特定一组线程唤醒,条件变量上的等待队列是单链表
public static void main(String[] args) throws InterruptedException {
ReentrantLock reentrantLock = new ReentrantLock();
Condition condition1 = reentrantLock.newCondition();
Condition condition2 = reentrantLock.newCondition();
condition1.await();
condition2.await();
}
CHL队列:CHL队列是双向链表,在AQS中直接等待的队列,AQS中用head,tail指向头尾。
public static void main(String[] args) throws InterruptedException {
ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.wait();
}
Node
上面说的等待队列中的节点是把线程做了一层封装,先看一下Node中重要的成员变量
static final class Node {
static final AbstractQueuedSynchronizer.Node SHARED = new AbstractQueuedSynchronizer.Node();
static final AbstractQueuedSynchronizer.Node EXCLUSIVE = null;
//waitStatus描述,暂时不讨论其含义
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
//前驱与后继节点
volatile AbstractQueuedSynchronizer.Node prev;
volatile AbstractQueuedSynchronizer.Node next;
//节点中包含的线程
volatile Thread thread;
//下一个等待的节点
AbstractQueuedSynchronizer.Node nextWaiter;
//...........
}
知道了上面的基础知识就可以手动实现一个不可重入锁了
自定义锁
class myLock implements Lock {
//独占锁,同步器内部类
class MySync extends AbstractQueuedSynchronizer {
@Override//加锁
protected boolean tryAcquire(int arg) {
//state变量加了volatile
if (compareAndSetState(0,1)){//设置当前线程
setExclusiveOwnerThread(Thread.currentThread());
}
return true;
}
@Override//解锁
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
//由于是独占锁,setState无需同步处理。但是必须在上一行之后,
//因为volatile保证前面的操作能被其他线程读取,所以先把持有锁的线程设置为null
setState(0);
return true;
}
@Override//是否持有独占锁
protected boolean isHeldExclusively() {
return getState() == 1;
}
public Condition newCondition(){
return new ConditionObject();
}
}
MySync sync = new MySync();
@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 l, TimeUnit timeUnit) throws InterruptedException {
return sync.tryAcquireNanos(1,timeUnit.toNanos(l));
}
@Override//解锁
public void unlock() {
sync.release(1);
}
@Override//创建条件变量
public Condition newCondition() {
return sync.newCondition();
}
}
发现没有,实现一个简易的自制锁是如此简单,因为AQS底层已经封装好了必要的方法,后面说的ReentrantLock也是在此模板上进行扩展
ReentrantLock
特点
不可打断模式:在未获得锁的时候被打断仅会加上打断标记,在未获得锁的过程中对打断置之不理,继续驻留在队列。获得锁后会继续运行,只是打断标记变为true
可打断模式:尝试获得锁的过程中被打断就不会进入获取资源的for循环,会终止获得锁的行为,直接抛出异常
try {
//如果获取失败进入阻塞队列,这时可以被其他线程interrupt打断
reentrantLock.lockInterruptibly();
} catch (InterruptedException e) {
//打断后操作
e.printStackTrace();
}
非公平锁:不检查AQS队列,直接加锁。默认不公平,公平锁会降低并发度,非必要不使用
公平锁:先检查AQS队列,如果队列中没有第二或者第二节点不是当前线程则不会去竞争锁
ReentrantLock获取锁后执行的代码在try块中执行, Lock比Synchronize更广泛,且需要手动加锁解锁。发生异常时要在finally中释放锁,否则会造成死锁。
特定唤醒
为了便于理解ReentrantLock相对于Synchronize的特性,我们来实现一个特定唤醒的例子
通过标志位flag和condition来做到指定唤醒某个线程
class Share{
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private int flag = 1;
public void a1() throws InterruptedException {
lock.lock();
try {
while (flag != 1){
c1.await();
}
System.out.println("do a1........");
flag = 2;
//c1执行完唤醒c2里的线程
c2.signal();
}finally {
lock.unlock();
}
}
public void a2() throws InterruptedException {
lock.lock();
try {
while (flag != 2){
c2.await();
}
System.out.println("do a2........");
flag = 1;
//c2执行完唤醒c1里的线程
c1.signal();
}finally {
lock.unlock();
}
}
}
本篇总结
此篇文章初步了解了AQS和ReentrantLock的概念及特性,下一篇我们会深入了解它们之间底层的实现