本文详细介绍AQS相关的知识
概述
AQS全称:Abstract Quened Synchronizer
思路
1.学习AQS的主要目的是了解其原理
2.提供自我技术水平
3.应对面试
4.先了解其场景,再学习如何使用,再掌握其原理
为什么要学习AQS
我们发现ReentrantLock和Semaphore有个共同的特点,即为闸门,都有协作,提取其工具类变成了AQS
同步类和AQS的关系
Semaphore与AQS关系如下图所示:
CountDownLatch为AQS关系如下图所示:
ReentrantLock与AQS关系如下图:
重要性
我们查看下那些类都使用了AQS,见下截图。
AQS核心部分
1.state:实现类不同则含义不同,在Semaphore中代表剩余的许可证的数量;在CountDownLatch代表还需要倒数的数量
相关的方法如下:
2.控制线程配合和抢锁的FIFO队列
此队列用来存放等待线程的队列,双向的链表
3.获取或者释放的重要方法
不同的实现类对应的获取和实现方法都不相同。
AQS用法
我们从CountDownLath、Semaphore、ReentrantLock中分析AQS的用法
CountDownLath中AQS用法
我们从构造、计数、countDown和await进行分析,首先我们看下构造:构造时,我们需要复制sycn,我们将count设置成了state
我们在分析类的继承关系:
接下来我们查看下await方法,如下所示:
private boolean doAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException { if (nanosTimeout <= 0L) return false; final long deadline = System.nanoTime() + nanosTimeout; final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head) { int r = tryAcquireShared(arg); if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return true; } } nanosTimeout = deadline - System.nanoTime(); if (nanosTimeout <= 0L) return false; if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); if (Thread.interrupted()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
Semaphore中AQS用法
我们重点分析下信号量中是如何获取的,即require方法的实现。
final int nonfairTryAcquireShared(int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }
上述代码是非公平时,获取信号量的方法,从代码中我可以看到使用了CAS的自旋操作,首先我们获取目前系统中剩余的信号量即为availabe;减去需要申请的信号量,
remaining代表剩余的信号量,当剩余信号量为负数时,说明此时申请失败,继续下一次的循环,当剩余信号量remaining大于等于0时,说明可以申请了,
继续执行CAS
操作修改state值,若修改成功,则获取成功;若修改失败,则代表被其他线程抢走了,继续执行循环直到申请到为止。
ReentrantLock中AQS用法
我们熟知ReenTrantLock是一把可重入锁,本实例我们从非公平的情况下进行分析,首先我看ReenTrantLock内部类的结构或者相关的方法。如下图所示:
接下来我们分析非公平的加锁的实现。
final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
从代码结构中可以看到:首先我们执行CAS的加锁操作,将state=0 修改成state=1;
若修改成功,则标记当前线程为锁的持有者;
若修改失败,则我们基于执行acquire方法,
接下来我们分析acquire方法的实现:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
下面代码为tryAcquire的实现。
我们可以看到:首先我获取state的值,如果state=0;说明锁没有被占用,然后CAS操作,设置当前线程持有锁;
如果state不等于0,判断是不是持有者,如果是,则进行重入操作,state+1;
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
下面代码为释放锁的代码实现,如下图:
protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
我们发现释放锁的时候,需要判断当前线程是不是持有锁,否则会抛出异常;若c=0,则直接释放;否则重入次数减去1