1。AQS是什么
2。AQS 用法,demo 效果
3 。AQS 原理,为什么能干成这样
4。AQS的扩展
什么是AQS
名词解释:JUC下 一个名为AbstractQueuedSynchronizer的类(java.util.concurrent.locks.AbstractQueuedSynchronizer)简称 AQS
功能简介:队列同步器,用来构建锁或者其他同步组件的基础框架。它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,作者 Doug lean,一个非常的老头。
AQS 使用效果
package com.emma.wangzhifeng.tr.juc;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class Mutex implements Lock {
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
@Override
protected boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int releases) {
assert releases == 1; // Otherwise unused
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
}
private final Sync sync = new Sync();
@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 timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return null;
}
}
//Test Demo
public class TestDemo1 {
static int count = 0;
static Mutex mutex = new Mutex();
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1000);
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
incrCount(countDownLatch);
}
}).start();
}
countDownLatch.await();
System.out.println(count);
}
private static void incrCount(CountDownLatch countDownLatch) throws InterruptedException {
//mutex.lock();
Thread.sleep(10);
System.out.println(count);
count++;
//mutex.unlock();
countDownLatch.countDown();
}
}
AQS 原理
AQS 方法初识
方法名称 | 方法描述 | 是否必须重写 |
---|---|---|
final void acquire(int arg) | 独占式获取同步状态,如果当前线程获取同步状态成功,该方法返回,否则,将会进入同步等待队列,改方法将会调用重写的tryAcquire方法 | 不能重写 |
boolean tryAcquire(int arg) | 独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再cas设置同步状态 | 必须重写 |
final boolean release(int arg) | 独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒,该方法将会调用重写的tryRelease方法 | 不能重写 |
boolean tryRelease(int arg) | 独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态 | 必须重写 |
acquire获取锁
// 入口
public final void acquire(int arg) {
// tryAcquire 失败后加入等待队列
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//如果线程在排队过程中有其他线程对其进行了中断,则此处将中断标志位复位
// 疑问1 :此处有何用 -》 parkAndCheckInterrupt方法配合
selfInterrupt();
}
/**
需要自己实现的
**/
@Override
protected boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
//cas 设置状态如果成功,获取锁成功,否则失败
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 向队列尾部增加一个节点
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//获取尾节点,如果尾节点不为null,通过cas将当前节点替换为尾节点,返回当前节点
if (pred != null) {
node.prev = pred;
//通过cas将当前节点设置为尾节点
if (compareAndSetTail(pred, node)) {
//将尾节点的nex指针指向当前节点
pred.next = node;
return node;
}
}
// 如果尾节点为null
enq(node);
//返回当前节点
return node;
}
private Node enq(final Node node) {
for (;;) {
//如果尾节点为null,则说明队列未被初始化
//通过cas将头节点和尾节点同时初始化未一个空节点
//循环继续,此时t!=null,走else
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 将当前节点的前一个节点设置为尾节点,
// 通过cas将当前节点设置为尾节点
// 将尾节点(等于头节点)的next 设置为当前节点
// 此时当前队列中有两个节点,空的头节点,尾节点既当前节点
// 退出当前循环
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
// 将当前节点增加到队列中
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
// 初始化中断状态为否
boolean interrupted = false;
for (;;) {
// 获取前一个节点
final Node p = node.predecessor();
// 如果前一个节点是头节点,尝试获取锁
// 如果获取锁成功,将当前节点设置为头节点(cas),将之前的头节点设置为null
// 以下设计要点,需要和release 方法一起看
// 此时如果第一次没有获取锁,则shouldParkAfterFailedAcquire方法返回false,将头节点设置为-1,继续 下一次循环
// 如果第二次循环依旧没有获取锁(非公平锁,被别的给抢了),那么shouldParkAfterFailedAcquire返回true。线程park。直到其他线程执行完release锁的时候,依旧会唤醒头节点的下一个节点。
// 这种设计方式,关键点在于release永远是头节点的下一个节点,不管当前执行的是否为头节点的线程
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 如果前一个节点不是头节点或者获取锁失败
// shouldParkAfterFailedAcquire 方法是将前一个节点的waitStatus设置为-1才会返回true
// 如果shouldParkAfterFailedAcquire 返回false,则会一直循环直到返回true,
// 然后执行parkAndCheckInterrupt方法,转到parkAndCheckInterrupt方法
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// 该方法目的为设置前一个节点的waitStatus 设置为-1
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//如果前一个节点状态为-1,返回true
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
// 如果前一个节点状态大于0,即1为取消状态,则需要向前寻找,一直找到状态不大于0的节点 realPre
// 将当前节点的pre 指针指向 realPre
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
// 只要pre节点状态大于0,即pre指针向前挪。node.pre 指向该节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
// 将状态不大于0的节点的next指针指向node 节点
//退出该方法后,该方法返回false,继续循环
pred.next = node;
} else {
// waitStatus此处只有俩个值的可能:0,-3
// 0:初始值
// -3: waitStatus值,指示下一个acquireShared应该无条件传播
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
// waitStatus 是0或-3,表明我们需要一个信号,但是还没有park,
// 调用方需要重试确定在park之前它确实不能获得锁
// 如果此处成功或失败 继续循环
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// park当前线程
// 如果当前线程为非阻塞状态,则方法执行到LockSupport.park 阻塞住,不会继续执行
// 如果当前线程为阻塞状态,则方法执行到LockSupport.park会立即返回,执行Thread.interrupted()将会将线程的中断状态设置为否,然后返回true,表示该线程是中断线程。
//下次循环LockSupport.park 会阻塞住非中断线程
// 当有LockSupport.unpark唤醒时,继续执行 Thread.interrupted。
// a。如果此时线程状态为未中断,parkAndCheckInterrupt返回false
// acquireQueued 继续循环,尝试获得锁
// 如果此时成功,acquireQueued返回false,线程非中断状态
// 如果此时失败,shouldParkAfterFailedAcquire返回true,调用parkAndCheckInterrupt线程继续阻塞
// b。如果此时线程因为在等待过程中状态被设置为了中断状态,parkAndCheckInterrupt返回true
// acquireQueued 继续循环,尝试获得锁
// 如果此时成功,acquireQueued返回true,线程中断状态,执行acquire selfInterrupt()方法将线程置为中断 状态;
//如果此时失败,shouldParkAfterFailedAcquire返回true,调用parkAndCheckInterrupt方法,因为当前线程为中断状态 LockSupport.park(this)会立即返回不会阻塞住该线程,此时调用Thread.interrupted()将中断状态转为非中断状态,parkAndCheckInterrupt返回true
//acquireQueued.interrupted=true,acquireQueued继续循环
//获取锁成功,则返回true,acquire selfInterrupt()设置当前线程中断状态
//获取锁失败,shouldParkAfterFailedAcquire返回true,调用parkAndCheckInterrupt方法,因为此时线程已经市非中断状态,所以线程阻塞。
private final boolean parkAndCheckInterrupt() {
// LockSupport.park
// 如果当前线程为非中断状态,则线程阻塞到这里
// 如果线程状态为中断状态,方法立刻返回(因为这个所以必须有下边的interrupted,配合使用)
LockSupport.park(this);
// 如果当前线程为中断状态,返回true,将线程至为 非中断状态
// 如果当前线程为非中断状态,返回false
return Thread.interrupted();
}
如果parkAndCheckInterrupt 方法不用Thread.interrupted,用的是Thread.isInterrupted,就没有了将中断线程设置为非中断线程的功能,则LockSupport.park(this) 一直不能阻塞当前中断线程,导致acquireQueued会一直循环下去,耗费cpu。
release释放锁
// 入口
public final boolean release(int arg) {
if (tryRelease(arg)) {
// 锁释放成功后,获取头节点,如果头节点存在且waitstatus 不是初始化状态,说明后边有节点在等待
// 唤醒的永远是头节点,
// 即使是非公平锁,一上来就抢到了锁,当它执行完释放的时候也是释放之前的那个头节点之后的节点
// 避免队列中的节点永远都拿不到锁
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒头节点后边的节点
unparkSuccessor(h);
return true;
}
return false;
}
/**
* 需要自己实现的
* 释放锁不需要cas原因:
* 不管是否可重入锁,出现问题的是多个线程之间操作,可重入锁也是同一个线程获取锁,一个线程执行程序是有序的
* 在入口处 已经控制了只有一个线程可以获得锁,所以同一时刻释放锁的时候,有且只有一个线程会被执行。
**/
@Override
protected boolean tryRelease(int releases) {
assert releases == 1; // Otherwise unused
if (getState() == 0) throw new IllegalMonitorStateException();
// 判断如果不是当前线程不是拥有锁的线程 拦截
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
// 为什么这里不需要cas?如果是可重入锁,这里会出现问题么?
setState(0);
return true;
}
/**
* 唤醒h后的一个有效节点
**/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
// 如果当前节点的状态为小于0 将当前节点状态设置为0初始值
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
// 获取当前节点的下一个节点s
// 如果下一个节点s是null,或者状态waitStatus>0 (取消)不能被唤醒,将s初始化为null
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// t从队尾向前寻找;只要节点状态小于0,s指针一直向前移动;直到 t等于当前节点
// 则s是离当前节点最近的一个状态小于等于0的下一个节点
// 代码解析
// t初始化为尾节点,每一次循环后如果t不等于当前节点, t = t.prev,t向前移动一个节点
// 循环内容:如果t的status 小于等于0,等于说明后边没有节点在等待,当时当前节点需要被唤醒
// s 等于当前t
// 整个循环结束的条件是 t等于当前节点,此时 s等于上一个状态小于等于0的t
// 说明s是下一个离当前节点最近的状态小于等于0的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 找到下一个状态小于等于0的节点,需要被唤醒
if (s != null)
// 唤醒后 s.thread 继续在上锁的地方 LockSupport.park(s.thread)向下继续执行
LockSupport.unpark(s.thread);
}
private static final boolean compareAndSetWaitStatus(Node node,
int expect,
int update) {
//unsafe native方法 c实现的,不深究
return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
}
AQS的扩展
ReentrantLock
支持一个线程多次获取锁,多次释放锁之后,其他线程才能拿到锁
公平锁 FairSync
获取锁的顺序是请求的绝对时间,也就是FIFO
ReentrantLock
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
//获取锁入口
final void lock() {
acquire(1);
}
/**
* 重写方法
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获得锁的状态
int c = getState();
if (c == 0) {
// 如果锁未被锁定,判断队列中是否有等待线程 hasQueuedPredecessors
// 如果没有,则尝试获取锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//将当前执行线程放入到锁中
setExclusiveOwnerThread(current);
return true;
}
}
// 如果当前锁是被锁定状态
// 判断当前线程与拥有锁的线程是否为同一个线程
// 如果是则将锁的状态+1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
AbstractQueuedSynchronizer
/**
* 如果队列中头节点后有其他等待线程则返回true
* 如果队列中中没有等待数据或者没有其他等待节点则返回false
**/
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
// 代码分析
// 如果头节点为空,则当前队列无等待线程
// 如果头节点不为空
// 头节点的下一个节点为空 说明队列中无等待线程(头节点是已经拿到锁的线程)
// 头节点的下一个节点不为空,如果下一个节点的线程等于当前线程,说明当前线程是第二个节点,按顺序也到它了
// 头节点的下一个节点不为空,如果下一个节点的线程不等于当前线程,说明下一个不是它,有其他等待线程
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
// 释放锁入口(公平与非公平实现逻辑相同)
public void unlock() {
sync.release(1);
}
/**
* AbstractQueuedSynchronizer 之前分析过了
**/
public final boolean release(int arg) {
// 全部释放后才会唤醒下一个节点
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
/**
* 重写方法
*/
protected final boolean tryRelease(int releases) {
// 状态减去当前释放的数字
int c = getState() - releases;
// 判断必须当前线程调用
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 锁是否全部释放 默认为否
boolean free = false;
// 如果状态为0 说明所有锁都释放了
if (c == 0) {
// 将当前线程置为null
free = true;
setExclusiveOwnerThread(null);
}
// 设置状态
setState(c);
// 如果全部释放 返回true,则release方法会唤醒头节点的下一个节点
// 如果返回false 不会唤醒下一个节点
return free;
}
非公平锁
非公平锁 并不是所有抢锁的线程一直争锁。根据代码发现,只是在有新的线程获取锁的时候,不会先排队,而是直接抢锁,如果此时抢到了,则执行顺序就排在了在队列中排队的线程,打破了FIFO,如果没有抢过还是进入对列排队,队列中的线程依旧是有序的执行FIFO。
/**
* 非公平锁
*/
ReentrantLock
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
//与公平锁的区别是,不管三七二十一先上来去抢锁
//假如此时恰好有一个线程释放了锁,唤醒了下一个线程。
// 但是下一个线程还没获得到锁的时候,当前线程就有可能获得锁
// 那么被唤醒的线程循环两次后会继续被park
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//走正常逻辑,加入队列尾部
acquire(1);
}
/**
* AbstractQueuedSynchronizer 之前分析过了
**/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果当前锁状态未锁定,再次尝试抢锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果当前锁状态为锁定,但是持有锁的线程就是当前线程,则重入
// 如果锁定且不等于当前线程,则尝试获取锁失败,加入队列尾部
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
OTHER
interrupt ,interrupted,isInterrupted
interrupt
public class TestThreadInterrupted {
public static void main(String[] args) {
//testInterrupt();
testInterrupted();
}
/**
* interrupt将线程设置为中断状态,但是不影响线程继续执行
*/
private static void testInterrupt() {
Thread.currentThread().interrupt();
System.out.println(Thread.currentThread().isInterrupted());
for (int i = 0; i < 10; i++) {
System.out.println("testInterrupt:" + i);
}
System.out.println(Thread.currentThread().isInterrupted());
if (Thread.currentThread().isInterrupted()) {
throw new RuntimeException("相应中断状态");
}
System.out.println("end!!!");
}
/**
* 如果当前线程状态为中断状态
* 第一次调用Thread.interrupted(),此时方法返回true,表示线程当前状态为中断状态,且该方法将线程状态设置为非中断状态,即归位。
* 第二次调用Thread.interrupted(),此时方法放回false,表示当前线程非中断状态,且不做状态转换
* 如果当前线程状态为非中断状态
* 第几次调用都是false,且不会改变当前线程状态
**/
private static void testInterrupted() {
//Thread.currentThread().interrupt();
System.out.println(Thread.currentThread().isInterrupted());
for (int i = 0; i < 10; i++) {
System.out.println("testInterrupted:" + i);
}
System.out.println("第一次:" + Thread.interrupted());
System.out.println("第一次调用之后状态" + Thread.currentThread().isInterrupted());
System.out.println("第二次:" + Thread.interrupted());
System.out.println("第二次调用之后状态" + Thread.currentThread().isInterrupted());
if (Thread.currentThread().isInterrupted()) {
throw new RuntimeException("相应中断状态");
}
System.out.println("end!!!");
}
}
注意:如果当前线程在park阶段,其他线程将park线程改为中断状态,则阻塞线程被唤醒,不再继续阻塞