AbstractQueuedSynchronizer (AQS)
抽象的队列同步器
提供一个框架,用于实现依赖于先进先出 first-in-first-out (FIFO)等待队列的阻塞锁和相关同步器(信号量、事件等)。这个类被设计成是大多数依赖单个原子整型来表示状态的同步器的有用基础形式(不同场景该整型值代表含义不同 具体通过子类来定义)。子类必须定义更改此状态的受保护(protected)方法,并定义该状态对于获取或释放此对象意味着什么。考虑到这些,这个类中的其他方法执行所有的排队和阻塞机制。子类可以维护其他状态字段,但是只有使用getState、setState和compareAndSetState方法操作的原子更新的int值才会被跟踪到同步方面。、
从DOC我们可以看出AQS就是一个规范提供了先进先出的模板,我们在根据它的设计思想去扩展自己的类,本质上跟synchronized类似只不过等待队列AQS可以有多个而synchronized只有一个Wet
AQS科技将线程进行Condition 分类进行等待(等待队列有多个) synchronized底层waitSet等待只能有一个等待队列, 当Condition 的某个等待队列线程被唤醒获取到资源执行,获取不到资源则进入first阻塞队列当中(若是公平锁直接进入阻塞队列) 类似 entryList 当waitSet被唤醒的线程获取不到资源贼进入entryList 中等待。 AQS同synchronized的区别就是synchronized只能有一个等待队列而AQS可以有多个等待队列。
AbstractQueuedSynchronizer 有一个很重要的变量
该变量是用来操作AQS锁的状态,具体情况的根据子类实现类型去做控制的
/**
* The synchronization state.
*/
private volatile int state;
AQS关键方法模板
在AQS当中 定义了很多protected方法 去操作不同锁 具体的逻辑是交给子类来进行操作实现
/*
尝试获取排它锁 尝试释放排它锁
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
/*
尝试获取共享锁 尝试释放共享锁
*/
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
/*
如果对当前(调用)线程以独占方式保持同步,则返回true。
具体逻辑由子类完成
如果是排它锁需实现排它锁的获取和释放
共享锁同理
排它锁返回 true 共享锁返回false
*/
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
当使用AbstractQueuedSynchronizer来实现不同的锁时 该state值在不同场景代表的含义也不同。如可重入锁state就代表当前资源加锁的次数 在获取锁时state+1 释放锁-1。(可多次,小于0抛异常)
ReentrantLock查看底层AQS的运行机制
我们通过示例ReentrantLock查看底层AQS的运行机制
public class MyTest1 {
private Lock lock = new ReentrantLock();
public void method() {
try {
this.lock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
this.lock.unlock();
}
}
public static void main(String[] args) {
MyTest1 myTest1 = new MyTest1();
IntStream.range(0, 10).forEach(i -> new Thread(() -> myTest1.method()).start());
}
}
ReentrantLock定义
doc:
一种可重入互斥(排它)锁,其基本行为和语义与使用同步方法和语句访问的隐式监视器锁相同,但具有扩展功能。
他提供了两种锁模式 一种是可重入互斥(排它)锁公平锁一种是可重入互斥(排它)锁非公平锁
内部有三个静态内部类
Sync泛化类继承AbstractQueuedSynchronizer(AQS)由于ReentrantLock定义为可重入互斥锁所以Sync重写了tryRelease 方法 排它释放锁 为什么不重写tryAcquire 主要原因非公平锁公平锁实现获取锁的逻辑不通。
ReentrantLock构造方法
ReentrantLock有个成员变量
/** 同步器提供所有实现机制 就是用来接收公平锁和非公平锁*/
private final Sync sync;
/**
*创建可重入锁定的实例。这相当于使用ReentrantLock(false)。
*/
public ReentrantLock() {
//创建非公平锁的同步对象 将对象赋值给变量
sync = new NonfairSync();
}
/**
*使用给定的公平策略创建可重入锁定的实例。
*
* fair–如果此锁应使用公平锁,则为true
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
当我们使用了 this.lock.lock();
实际上是调用了 sync.lock();
不同的锁机制执行的方法也不同
/**
* Acquires the lock.
*
* <p>Acquires the lock if it is not held by another thread and returns
* immediately, setting the lock hold count to one.
*
* <p>If the current thread already holds the lock then the hold
* count is incremented by one and the method returns immediately.
*
* <p>If the lock is held by another thread then the
* current thread becomes disabled for thread scheduling
* purposes and lies dormant until the lock has been acquired,
* at which time the lock hold count is set to one.
*/
public void lock() {
sync.lock();
}
由图可以看出不同锁会使用不同的策略
如示例为例private Lock lock = new ReentrantLock();
很明显我们使用的是非公平可重入互斥锁
我们可以查看源码
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* 执行锁定
*/
final void lock() {
/*
采用cas的方式原子性更改变量状态State 由0改为1
*/
if (compareAndSetState(0, 1))
// 为true则获取锁成功 设置当前拥有独占访问权限的线程 表示该线程独享资源
setExclusiveOwnerThread(Thread.currentThread());
else
/*变量未改成功 此时说明当前线程获取资源失败了
此时就会调用AQS的方法将当前线程放入阻塞队列*/
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
/*
执行不公平的tryLock。tryAcquire是在子类中实现的,
但这两个类都需要trylock方法的非空尝试。
*/
return nonfairTryAcquire(acquires);
}
}
由上图可以看出当公平锁在获取锁时无论什么情况都会先进入阻塞队列
AbstractQueuedSyncharonizer.acquire
/**
在独占模式下忽略,中断。通过调用至少一次tryAcquire来实现,并在成功时返回。否则,线程将进入队列排队
,可能会反复阻塞和取消阻塞,调用tryAcquire直到成功。此方法可用于实现方法锁。锁.
意思获取锁通过tryAcquire来实现 返回true获取成功 未成功则阻塞并防止到阻塞队列
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
//在这地方我们就用到了NonfairSync.tryAcquire
//如果是公平锁就会调用公平锁的tryAcquire
//只要调用此方法当前线程都会包装成Node放置到阻塞队列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
ReentrantLock.nonfairTryAcquire 非公平锁获取
/**
执行不公平的tryLock。tryAcquire是在子类中实现的,
但这两个类都需要trylock方法的非空尝试。
*/
final boolean nonfairTryAcquire(int acquires) {
//获取当前的线程
final Thread current = Thread.currentThread();
//拿到当前锁的状态
int c = getState();
if (c == 0) {
/*当前锁的状态修改为1 并且当前线程独享资源时 才返回true
非公平锁这种情况下不需要去进行排队如果刚好被唤醒拿到锁直接去执行资源
拿不到才放入阻塞队列
符合这种逻辑
*/
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//判断当前线程是不是这个资源的拥有者 因为是可重入锁可以多次上锁
else if (current == getExclusiveOwnerThread()) {
//是当前线程又上锁的话State+1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//不是则进行等待
return false;
}
AbstractQueuedSynchronizer.ConditionObject 等待队列
- 作为锁实现基础的AbstractQueuedSynchronizer的条件实现。
AbstractQueuedSynchronizer.Node 等待队列类
DOC截取
- 等待队列节点类。
- 为了排队进入一个CLH锁,你需要把它作为一个新的尾部进行原子拼接。要出列,只需设置head字段。(意思新增阻塞队列)