AQS和CAS

Posted anhaogoon

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AQS和CAS相关的知识,希望对你有一定的参考价值。

一、AQS

什么是AQS

fifo队列 + 原子int(表示状态)

原子int:AtomicInteger这个类的存在是为了满足在高并发的情况下,原生的整形数值自增线程不安全的问题;

AQS(AbstractQueuedSynchronizer),AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。这个抽象类被设计为作为一些可用原子int值来表示状态的同步器的基类。如果你有看过类似 CountDownLatch 类的源码实现,会发现其内部有一个继承了 AbstractQueuedSynchronizer 的内部类 Sync。可见 CountDownLatch 是基于AQS框架来实现的一个同步器.类似的同步器在JUC下还有不少。(eg. Semaphore)

AQS用法

如上所述,AQS管理一个关于状态信息的单一整数,该整数可以表现任何状态。比如, Semaphore 用它来表现剩余的许可数,ReentrantLock 用它来表现拥有它的线程已经请求了多少次锁;FutureTask 用它来表现任务的状态(尚未开始、运行、完成和取消)。

技术图片

java可重入锁-ReentrantLock实现细节

ReentrantLock支持两种获取锁的方式,一种是公平模型,一种是非公平模型。在继续之前,咱们先把故事元素转换为程序元素。

技术图片 

咱们先来说说公平锁模型:

初始化时, state=0,表示无人抢占了打水权。这时候,村民A来打水(A线程请求锁),占了打水权,把state+1,如下所示:

技术图片

线程A取得了锁,把 state原子性+1,这时候state被改为1,A线程继续执行其他任务,然后来了村民B也想打水(线程B请求锁),线程B无法获取锁,生成节点进行排队,如下图所示:

技术图片

初始化的时候,会生成一个空的头节点,然后才是B线程节点,这时候,如果线程A又请求锁,是否需要排队?答案当然是否定的,否则就直接死锁了。当A再次请求锁,就相当于是打水期间,同一家人也来打水了,是有特权的,这时候的状态如下图所示:

技术图片

到了这里,相信大家应该明白了什么是可重入锁了吧。就是一个线程在获取了锁之后,再次去获取了同一个锁,这时候仅仅是把状态值进行累加。如果线程A释放了一次锁,就成这样了:

技术图片

仅仅是把状态值减了,只有线程A把此锁全部释放了,状态值减到0了,其他线程才有机会获取锁。当A把锁完全释放后,state恢复为0,然后会通知队列唤醒B线程节点,使B可以再次竞争锁。当然,如果B线程后面还有C线程,C线程继续休眠,除非B执行完了,通知了C线程。注意,当一个线程节点被唤醒然后取得了锁,对应节点会从队列中删除。 

 

 

源码

AbstractQueuedSynchronizer(AQS)
//队列头
private transient volatile Node head;
//队列尾
private transient volatile Node tail;
同步状态
private volatile int state;

/**
* 获取锁,
/
public final void acquire(int arg) {
//尝试获取锁
if (!tryAcquire(arg) &&
//自旋获取锁
acquireQueued(

addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

/**
* 获取锁,如果获取失败则会进入CLH队列
/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 获取原队尾
Node pred = tail;
if (pred != null) {
node.prev = pred;
//用cas更新,pred是原来队尾,作为预期值,node作为新值
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//前面cas更新失败后,再enq方法中循环用cas更新直到成功
enq(node);
return node;
}

/**
* 自旋获取锁
/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//拿到当前节点的前置节点
final Node p = node.predecessor();
//如果当前节点的前置节点是头节点的话,就再次尝试获取锁
if (p == head && tryAcquire(arg)) {
//成功获取锁后,将节点设置为头节点
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//更改当前节点前置节点的waitStatus,只有前置节点的waitStatus=Node.SIGNAL
//当前节点才有可能被唤醒。如果前置节点的waitStatus>0(即取消),则跳过取更前面的节点
if (shouldParkAfterFailedAcquire(p, node) &&
//通过Unsafe.park来阻塞线程
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

/**
* 线程释放锁,从前面可以知道,获取到锁的线程会设置为CLH队列的头部。
这里如果tryRelease返回true,且head的waitStatus!=0。就会更新head的waitStatus为0并且 唤醒线程head.next节点的线程
/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//更新head的waitStatus为0并且唤醒线程head.next节点的线程
unparkSuccessor(h);
return true;
}
return false;
}

/**
* 更新head的waitStatus为0并且唤醒线程head.next节点的线程
/
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
//waitStatus不是取消状态,就设置成0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//获取下个waitStatus不为取消的Node
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//LockSupport.unpark是调用了Unsafe.unpark,唤醒线程
if (s != null)
LockSupport.unpark(s.thread);
}


/**
* CLH队列(FIFO双端双向队列)
/
static final class Node {
//用于标识共享锁
static final Node SHARED = new Node();
//用于标识独占锁
static final Node EXCLUSIVE = null;
/因为超时或者中断,节点会被设置为取消状态,被取消的节点时不会参与到竞争中的,他会一直保持取消状态不会转变为其他状态
static final int CANCELLED = 1;
//当前节点释放锁的时候,需要唤醒下一个节点
static final int SIGNAL = -1;
//节点在等待队列中,节点线程等待Condition唤醒
static final int CONDITION = -2;
//表示下一次共享式同步状态获取将会无条件地传播下去
static final int PROPAGATE = -3;

//等待状态
volatile int waitStatus;
//前驱节点
volatile Node prev;
//后继节点
volatile Node next;
//节点线程
volatile Thread thread;
//
Node nextWaiter;
}

二、CAS

CAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

在JAVA中,sun.misc.Unsafe 类提供了硬件级别的原子操作来实现这个CAS。 java.util.concurrent 包下的大量类都使用了这个 Unsafe.java 类的CAS操作。至于 Unsafe.java 的具体实现这里就不讨论了。

以上是关于AQS和CAS的主要内容,如果未能解决你的问题,请参考以下文章

CAS和AQS

AQS和CAS

06 CAS的原理和AQS

从 synchronized www2015338com到 CAS 和 19908836661AQS

从 synchronized 到 CAS 和 AQS - 彻底弄懂 Java 各种并发锁

CAS和AQS一文搞懂