AQS全名为AbstractQueuedSynchronizer:抽象的同步队列
CAS全名为CompareAndSet:比较与设置原子操作
今天咱们以ReentrantLock为例来讲解AQS与CAS
首先咱们看下ReentrantLock类图
这里包括两个部分
ReentrantLock类图
从类图可以看出,ReentrantLock实现了Lock接口
//Lock接口中定义这这么多方法,咱们重点看下lock()与unlock()方法
lock() //获取锁
lockInterruptibly()
tryLock()
unlock()//释放锁
newCondition()
//ReentrantLock 有三个内部类
1、abstract static class Sync extends AbstractQueuedSynchronizer
2、static final class FairSync extends Sync
3、static final class NonfairSync extends Sync
Sync类图
从类图出可以看出Sync同步类有两个子类,
Sync继承了抽象类AbstractQueuedSynchronizer,也就是Sync与它的子类都是同步器。
FairSync:公平同步器,又称公平锁。
NonfairSync:非公平同步器,又称非公平锁。
AbstractQueuedSynchronizer
了解AbstractQueuedSynchronizer首先需要了解下它的属性及内部类
//首先先了解下Node这个类,它是队列对应的Node节点
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
//waitStatus值为1时表示该线程节点已释放(超时、中断),已取消的节点不会再阻塞
static final int CANCELLED = 1;
//waitStatus为-1时表示该线程的后续线程需要阻塞,即只要前置节点释放锁,就会通知标识为 SIGNAL 状态的后续节点的线程
static final int SIGNAL = -1;
//waitStatus为-2时,表示该线程在condition队列中阻塞(Condition有使用)
static final int CONDITION = -2;
//waitStatus为-3时,表示该线程以及后续线程进行无条件传播(CountDownLatch中有使用)共享模式下, PROPAGATE 状态的线程处于可运行状态
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;
}
}
private transient volatile Node head; //队列头节点
private transient volatile Node tail; //队列尾节点
private volatile int state; //同步状态
private transient Thread exclusiveOwnerThread; //执行权限线程
//咱们这里稍微重复下 volatile这个关键字
//这个关键子解决了两个问题 1、可见性 2、有序性
//可见性是指多个线程共享同一个变量,如果有线程修改了这个变量修改这个属性,另一个线程是否可以同步刷新到别的线程可见。
private static final Unsafe unsafe = Unsafe.getUnsafe(); //直接操作内存辅助类
ReentrantLock构造方法
//默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//可以传个参数进来标志是公平锁还是非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock.lock()方法
咱们这次以非公平锁为例NonfairSync
//获取锁
final void lock() {
//开始先尝试进行CAS操作
if (compareAndSetState(0, 1))
//如果设置成功,同时设置AQS被当前线程占用
setExclusiveOwnerThread(Thread.currentThread());
else
//获取失败重新再次获取
acquire(1);
}
AbstractQueuedSynchronizer.acquire(1)方法及重新获取
//尝试获取
public final void acquire(int arg) {
//首先咱们梳理下方法的执行顺序
//1、tryAcquire 2、addWaiter(Node.EXCLUSIVE) 3、acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
//tryAcquire(arg) 获取资源,获取到返回true,获取不到返回false。这里只有两种情况下能获取到 1、AQS状态为0,既未被其他线程持有 2、当前线程已经持有
//如果tryAcquire未获取资源会进行加入队列,让虚拟机调度
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//把已经加入队列的线程休息一会
selfInterrupt();
}
Sync.tryAcquire(1):咱们点进去发现这个方法是一个空的实现,但是呢,Sync这个子类一个实现了,那么接下来咱们继续了解下这个具体实现
//非公平锁尝试获取状态
final boolean nonfairTryAcquire(int acquires) {
//获取到当前线程
final Thread current = Thread.currentThread();
//获取AQS的状态 初始值是0
int c = getState();
//如果AQS未被占用
if (c == 0) {
//进行CAS操作(比较并交换)
if (compareAndSetState(0, acquires)) {
//获取成功设置当前线程权限为独占
setExclusiveOwnerThread(current);
return true;
}
}
//判断当前线程是否是独占线程,这里小伙伴们就有疑问了,为什么要这么做呢?答案是可重入锁
//什么是可重入锁呢,举个例子,一个线程调用了一个方法,这个方法中有lock.lock()获取锁,这个代码块中呢又调用了lock.lock()获取锁,那么第二次就可以直接在这里直接拿到权限
else if (current == getExclusiveOwnerThread()) {
//从这里可以看出,同一个线程多次调用加锁方法后肯定会执行当前代码
int nextc = c + acquires;
//这里做了一次验证,就是验证咱们代码里是否是一次加锁多次解锁的操作
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// state = newState; state设置完成后会直接同步到主存,主存发现其他线程持有这个对象,那么就会通知并修改state(MESI,有兴趣的可以看下JVM内存管理篇文档)
setState(nextc);
return true;
}
return false;
}
咱们接着了解下AQS.addWaiter(Node.EXCLUSIVE)方法
//这里mode为null
private Node addWaiter(Node mode) {
//创建一个Node节点
Node node = new Node(Thread.currentThread(), mode);
//把尾节点赋值给pred
Node pred = tail;
//如果有尾节点
if (pred != null) {
//设置当前节点的前置节点为tail
node.prev = pred;
if (compareAndSetTail(pred, node)) {
//如果加入尾节点成功设置尾节点的下一个节点为当前node节点
pred.next = node;
//返回新加入的节点
return node;
}
}
//如果尾节点为null或者设置失败,继续将node插入节点
enq(node);
return node;
}
咱们继续看addWaiter方法中调用的AQS.enq()方法
//这个方法就解决了一个问题,把node节点插入到尾部并返回设置前的尾节点,由于是CAS操作必须进行循环
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 必须进行初始化
//如果尾节点为null,先对头节点设置为新创建的node
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
那么咱们接着看下AQS.acquireQueued(tail,arg)获取资源
//前面讲了,这个node节点就是新node插入队列后的前置节点
//注意,前置节点不一定是尾节点(因为方法执行到此方法有可能涉及到线程切换)
//从源码逻辑可以看出多线程去竞争资源,谁抢到谁有执行权(非公平锁)
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取node的前置节点,如果为null直接抛出异常
//这里会循环获取node的前置节点
final Node p = node.predecessor();
//enq方法可以看出,head就是一个空的node节点,它的next就是为null
//p == head的时候说明已经实例化了,再次调用nonfairTryAcquire方法尝试获取占用状态(当前线程持有)
if (p == head && tryAcquire(arg)) {
//重新尾节点设置为头节点
setHead(node);
p.next = null; // help GC 头节点的下一个节点为null (空头),就是当前线程已经获取到了资源,把前一个对象释放
failed = false;
return interrupted;
}
//shouldParkAfterFailedAcquire(p, node) 根据前置节点判断是否阻塞当前线程
//parkAndCheckInterrupt() 阻塞当前线程并返回被打断状态
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//获取资源失败后,重新释放资源
if (failed)
cancelAcquire(node);
}
}
//根据前置节点判断是否阻塞当前线程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前置节点waitStatus
int ws = pred.waitStatus;
//如果前置节点waitStatus为-1,前置节点已经释放资源, 直接返回true
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
//如果前置节点的状态大于0了 一只向前找,直到找到小于等于0的那个节点 直到
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//前置节点前置节点的状态为-1 ,便于shouldParkAfterFailedAcquire返回true
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
ReentrantLock.unlock()释放资源
public void unlock() {
sync.release(1);
}
//AQS.release
public final boolean release(int arg) {
//释放锁
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
Sync.tryRelease
//Sync.tryRelease
//从释放资源可以看出lock是可重入锁,使用状态控制重入次数
//也就是咱们使用的时候如果有lock()方法,就一定要释放
protected final boolean tryRelease(int releases) {
//获取状态
int c = getState() - releases;
//如果当前持有资源的线程不是当前线程抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果状态==0 代表当前线程无重入了
if (c == 0) {
free = true;
//设置当前AQS为被线程占用
setExclusiveOwnerThread(null);
}
//重置状态
setState(c);
return free;
}
AQS与CAS就讲到这里了,如果有帮到您帮博主点点关注啦~~