目录
AQS简介
AbstractQueuedSynchronizer位于java.util.concurrent.locks包下,是一个用于构建锁和同步器的框架,是除了java自带的synchronized之外的锁机制。ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier等均是基于AQS构建出来的。
其维护了:
- 一个volatile类型的state变量,用于描述锁是否被占有,如果未占有=0,如果已占有>=1,首次占用=1,每重入一次+1,因此重入加锁多少次就要解锁多少次,每次解锁-1,直到0时释放锁
- 一个FIFO的线程等待队列,当锁已被某线程占用,再次发生线程竞争,该线程会进入队列排队
- 线程拥有者,存储占用当前锁的线程,方便重入
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private volatile int state;// 锁状态,加锁成功则为1,重入+1,解锁为0
private transient volatile Node head;// 队首
private transient volatile Node tail;// 队尾
/**
* Node结点
*/
static final class Node {
volatile Node prev;// 上一个结点
volatile Node next;// 下一个节点
volatile Thread thread;// 节点线程
}
}
其继承的AbstractOwnableSynchronizer类维护了线程持有者
public abstract class AbstractOwnableSynchronizer implements java.io.Serializable {
/**
* 锁的线程持有者
*/
private transient Thread exclusiveOwnerThread;
}
其功能主要分为独占锁和共享锁:
- 独占锁:一个时间只能有一个线程拿到锁,可重入状态以外,state只有0和1两种。例如ReentrantLock,state!=0且持有锁的线程不是当前线程,则拿不到锁
- 共享锁:一个时间可以有多个线程拿到锁协同工作,state可以是任意正整数。例如CountDownLatch,state!=0就拿不到锁
自定义同步器首先继承AQS类,然后重写以下方法:
- protected boolean tryAcquire(int arg):独占方式获取锁。成功返回true
- protected boolean tryRelease(int arg):独占方式释放锁。成功返回tre
- protected int tryAcquireShared(int arg):共享方式获取锁。<0失败,=0成功但没有可用资源,>0成功且有剩余资源
- protected boolean tryReleaseShared(int arg):共享方式解锁。成功返回true
前言概述
ReentrantLock,初始化state=0,表示锁未被占用,当过来一个线程调用tryAcquire以独占方式获取锁,此时state+1,锁被占用后,其他线程再来想获取,就会失败,然后去队列中排队,按照先进先出的方式依次获取锁。如果新来的线程还是刚刚已经占用锁的线程,则可以直接获取锁,并且state再+1,即可重入锁。ReentainLock.unLock方法调用tryRelease以独占方式释放锁,此时state-1,直到state=0,锁完全释放。
CountDownLatch,一般在一个主线程下存在N个子线程分别执行,初始化state=N,每一个子线程执行结束,调用countDown,state-1,所有子线程执行完毕,此时state=0,调用tryReleaseShared方法释放锁,其他线程才能获取锁。使用await方法进行阻塞,内部采用公平锁机制,新来的线程想要获取锁,如果发现state!=0,就从队尾入队,如果发现自己是队头,就采用tryAcquireShared方法获取锁,获取不到就死循环一直获取,新来的一直从队尾入队,直到state=0,拿到锁,队列里的线程逐个变成队头,逐个拿到锁,逐个执行。
ReentrantLock源码实现【独占锁】
ReentrantLock默认为非公平锁,其构造方法可以根据入参的true和false手动设定公平/非公平
/*
* 默认非公平锁
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/*
* 手动指定 公平/非公平
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁非公平锁区别
公平锁:严格遵循先来后到,如果当前锁被占用且队列中还有没出队的线程,就乖乖去排队,一直到轮到自己。
非公平锁:唯一的区别就是,线程来了,他如果发现没加锁,不会去检查队列里有没有,会直接CAS获取锁,获取到了就是获取到了,那么队列里的就得一直排队,因此有被饿死的风险,非公平锁可以在一定程度上提高吞吐量。
源码分析
reentrantLock.lock();
公平锁
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();// 获取当前线程
int c = getState();// 查看锁的状态
if (c == 0) {// 如果没被占用继续往下走
// 如果不需要排队,就开始CAS,CAS也成功了,就把当前线程作为持有这把锁的线程
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果线程已经被占用了,而且是当前线程,那就给state继续+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;
}
/**
* 查询是否需要放到队列里进行排队
* 队列没有被初始化
*/
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
/**
* 入队操作,直接插入队尾
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
/**
* 将未拿到锁的线程入队【插入队尾】
* tail.next = node;node.prev = tail
*/
private Node enq(final Node node) {
for (;;) {// 死循环
Node t = tail;
if (t == null) {
// 当队列尾结点为空,则新建一个空的Node节点,并赋值给head和tail
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 第一遍循环结束,tail!=null,于是开始走下面的代码
node.prev = t;// 尾结点后面插入node,即入队操作
if (compareAndSetTail(t, node)) {
t.next = node;// 双向链表,next也要赋值
return t;// 退出循环
}
}
}
}
/**
* 线程阻塞(在最终阻塞前,会自旋两次,仍然获取不到锁再进行阻塞)
*/
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;
}
// shouldParkAfterFailedAcquire第一次执行返回false,但内部会改变状态
// 第二次执行返回true,然后执行parkAndCheckInterrupt阻塞线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
非公平锁
/**
* 新的线程来了,直接CAS,才不管队列里有没有,如果队列里有,一直轮不到 就等着
*/
final void lock() {
// CAS比较state,当前为0则加锁成功,并设置当前线程为锁的拥有者
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
// 否则开始入队操作
else
acquire(1);
}
/*
* 看当前锁是否可占用,如果可以,不排队,直接占【区别于公平锁】
*/
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;
}
当线程交替执行,其每次判断的时候总能拿到锁,因此不用往队列里面放,也就不用切换内核态挂起线程,一直在JDK层面运行,因此非常快。
CountdownLatch源码实现【共享锁】
在完成一组线程操作之前,允许一个或多个线程等待,直到前一组全部完成。内部采用公平锁和共享锁机制。
初始化
构造方法:传入一个初始化数值>=0,由于Sync类继承的AQS,因此初始化是修改AQS中state值为当前值,对应CountDownLatch计数器初始值。
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
public class CountDownLatch {
// Sync为CountDownLatch的内部类,同时继承AQS,因此CountDownLatch底层由AQS实现
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
}
}
protected final void setState(int newState) {
state = newState;
}
await方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)// 共享锁方式获取锁,<0说明没拿到锁,往下执行
doAcquireSharedInterruptibly(arg);
}
/**
* 共享锁方式获取锁,state=0说明拿到锁返回1,拿不到返回-1
*/
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 新建一个共享结点,并且入队尾
final Node node = addWaiter(Node.SHARED);// static final Node SHARED = new Node();
boolean failed = true;
try {
for (;;) {// 自旋
final Node p = node.predecessor();// 获取node的上一个结点
if (p == head) {
// 如果node上一个结点就是head,说明node在队首
// 那就直接开始获取锁
int r = tryAcquireShared(arg);
// 如果r=-1,说明没拿到锁,那就继续死循环等着
if (r >= 0) {
// 如果获取锁成功,就把当前结点设置为head头结点,给下一个结点把队首位置腾出来
setHeadAndPropagate(node, r);
p.next = null;// 方便GC回收旧的head结点
failed = false;
return;
}
}
// 如果还是没拿到锁,找到有效的前驱结点后,使用park方法将线程挂起,直到被unpark
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
该Node是否可以被安心挂起。
判断自己的前驱waitStatus是否已经是SIGNAL,如果是,就可以放心挂起了,如果不是,需要找到有效的前驱
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 前驱结点的waitStatus已经=SIGNAL,就等着释放了
return true;
if (ws > 0) {
// 如果发现前驱>=,即=CANCELLED,表明前驱线程因超时或中断而取消执行
// 因此直接跳过,寻找后面符合条件的前驱结点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 前驱的waitStatus=0或PROPAGATE,将其设置为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
countDown方法
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {// 判断计数器是否=0,=0说明拿到锁了,往下走
doReleaseShared();
return true;
}
return false;
}
/**
* 判断AQS中state的值是否>0,若>0则减1
*/
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
// 如果state已经=0,说明已经有其他线程发出唤醒信号,这里无需再次唤醒
if (c == 0)
return false;
int nextc = c-1; // 大于0就-1
if (compareAndSetState(c, nextc)) // CAS比较state
// 如果state还是0,那就返回true开始释放共享锁
return nextc == 0;
}
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// Node节点的waitStatus变量如果为SIGNAL,说明后面挂起的结点需要被唤醒
if (ws == Node.SIGNAL) {
// 然后把waitStatus状态重置为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
unparkSuccessor(h);// 唤醒后继结点
}
else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head)
break;
}
}