概述
AQS 全称AbstractQueuedSynchronizer,其实就是一个抽象类。它定义了一套多线程访问共享资源的同步器框架。
其实其内部主要是通过定义了一个共享资源(volate int state)和双向队列。当多线程抢夺共享资源时,如果没有抢占成功的线程就会被分配到双向队列中阻塞等待。
通过源码分析AQS
想向一个场景,当多线程同时竞争锁时,此时竞争到锁的线程继续执行,那么没有竞争到锁的线程不得不阻塞等待。那么在这里,其他线程阻塞,等待的逻辑时怎样的?这些被阻塞的线程又是如何被唤醒的呢?
接下来我们看一段代码,如下:
public class LockDemo {
//定义一个重入锁
static ReentrantLock lock = new ReentrantLock();
static int count = 0;
static void add() {
lock.lock();
try {
Thread.sleep(100);
System.out.println("当前线程:" + Thread.currentThread().getName() + "正在执行加法操作");
count++;
LockDemo.sub();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
static void sub() {
lock.lock();
System.out.println("当前线程:" + Thread.currentThread().getName() + "正在执行减法操作");
try {
Thread.sleep(100);
count--;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(LockDemo::add, "thread" + i).start();
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("count:" + count);
}
}
以上代码,我们定义了一个重入锁,我们创建了10个线程来竞争这个锁。竞争到的线程可以继续执行,没有竞争到的只能在lock.lock()处阻塞等待。我们接下来分析这段代码的整个过程来讲解AQS的原理。
ReentrantLock构造方法
创建重入锁时,默认采用非公平锁
public ReentrantLock() {
//首先创建重入锁时,默认采用非公平锁
sync = new NonfairSync();
}
lock()方法
调用lock()方法时默认调用的时非公平锁的lock()方法
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() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
compareAndSetState(0, 1):如果当前线程抢占到资源,那么就将state(private volatile int state;默认为0)赋值为1,并且将当前线程赋值给变量(private transient Thread exclusiveOwnerThread)
acquire()方法
如果当前线程获取到锁就直接返回,否者进入到等待队列,直到获取到资源。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire()
final boolean nonfairTryAcquire(int acquires) {
//记录当前线程
final Thread current = Thread.currentThread();
//获取当前锁对象的State
int c = getState();
//如果state=0.说明当前没有线程占有锁资源
if (c == 0) {
//这个时候需要锁资源进行一次抢占(插队)。(这里恰恰体现了不非公平锁的含义,因为这个时候等待对列中可能还存在其他等待线程)
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//这里的这个方法getExclusiveOwnerThread()其实是返回上文说的exclusiveOwnerThread变量。因为这个变量记录的是当前获取所资源的线程对象,这里进行比较就是为了判断是否是重入锁。如果是重入锁,直接将共享资源state加1即可。
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;
}
tryAcquire()方法整体逻辑步骤如下:
- 记录当前线程,获取当前锁对象的state变量值
- 通过state值来判断当前锁对象是否被其他线程占有,如果没有,这个时候该线程可以抢占一次锁资源。这里要明确的是,这里所说的”抢占“实际上是”插队“的意思,因为目前等待队列中可能存在其他线程正在排队等待获取锁资源。这里的"抢占"实际上是非公平锁的含义。
- 判断是否是可重入锁。如果是,直接将state加1。返回true。从这里可以看出,state记录了当前线程进入了几重锁。
- 2,3条件均不满足说明该线程暂时无法抢占到锁资源,返回false。
addWaiter()方法
private Node addWaiter(Node mode) {
//将当前线程放入到一个Node中
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//tail第一次默认为空
Node pred = tail;
//如果尾节点不为空,说明队列不为空,则直接将该节点插入到尾部
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
enq()
//将带有当前节点的node入队
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
//如果队列为空,则将头节点和尾节点指向一起
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
//将新节点插入到尾部
t.next = node;
return t;
}
}
}
}
acquireQueued()
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;
}
//如果该线程可以休息,则park()
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire()
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前驱的状态
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
//如果为此状态,则直接park休息
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
否则一直从后向前找到一个合适的状态
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 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.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
//调用park()使线程进入waiting状态 等待unpark()或interrupt()唤醒自己
LockSupport.park(this);
//如果被唤醒,查看自己是不是被中断的
return Thread.interrupted();
}
以上是多线程情况下抢占lock()时的流程。以下是调用unlock()
public void unlock() {
sync.release(1);
}
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;
}
protected final boolean tryRelease(int releases) {
//将当前的state-1
int c = getState() - releases;
//如果已经抢占到锁的线程不等于当前线程,直接报错
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//当c==0时,才算是彻底释放锁,这里考虑到了重入锁
if (c == 0) {
free = true;
//直接将当前抢占到锁的线程变量设置为空
setExclusiveOwnerThread(null);
}
setState(c);
//当彻底释放锁,即当前state为0时,返回true
return free;
}
unparkSuccessor()
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.
*/
//获取当前节点状态
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.
*/
//
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;
}
if (s != null)
//唤醒线程
LockSupport.unpark(s.thread);
}