java多线程高级-AQS(三)
前面第一章介绍了锁的结构,现在介绍java里面的java.util.Concurrent包的里面的基石,AQS。
什么是AQS?
AbstractQueuedSynchronizer(后面简称AQS)AQS是JDK1.5提供的一个基于FIFO等待队列实现的一个用于实现同步器的基础框架,这个基础框架的重要性可以这么说,JUC(java.util.Concurrent)包里面几乎所有的有关锁、多线程并发以及线程同步器等重要组件的实现都是基于AQS这个框架。AQS的核心思想是基于volatile int state这样的一个属性同时配合Unsafe工具对其原子性的操作来实现对当前锁的状态进行修改。当state的值为0的时候,标识改Lock不被任何线程所占有。
AQS源码分析
AQS大致实现流程图
图说明:
private Lock lock = new ReentrantLockSource();
public void serviceA() {
lock.lock();
....
lock.unlock();
}
当执行lock.lock()时,就发生了我们图中的关系图了。
假如现在有两个线程(线程1,线程2)同时执行到了lock()方法了,线程1,2开始到了NonfairSync,做了一次尝试获取锁(CAS)明显有且只有一个线程能获取到锁,开始设置lock对象的全局变量,保证不让其它线程往下执行。
假如线程获取到了,1开始执行lock()方法后面的代码;线程2开始进入下一步中。
线程1:cas获取成功,往下执行代码;
线程2:cas失败,开始进入创建队列慢慢路;线程2开始下面几步创建等待队列
- 在创建node节点的时候看看线程1释放了锁没有,释放了,那接着往下执行,就不创建队列;
- 线程1还是没有释放锁,开始创建node节点,查看是否有队列了,没有创建队列,然后跟在队列头后面;如果有队列了,就跟在队列最后面;然后返回node节点。
- 开始检查队列中是否有node可以获取锁了,可以则当前线程可以跳出无线循环,往下执行代码了。当前队列没有一个获取锁的,则无限循环继续查找是否有node可以获取线程。(每个线程都在无限循环,当head节点指向自己线程的node时就可以跳出循环了)
开始竞争执行权
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() {
//线程1先进入尝试设置状态,如果成功则获取到执行权
//线程1获取锁,设置状态=1;
if (compareAndSetState(0, 1)) {
//标示当前已经获取了锁。
setExclusiveOwnerThread(Thread.currentThread());
} else
//线程2没有获取锁,需要做一些列事:创建队列或者追加队列后面;阻塞自己,不在往下继续执行。
acquire(1);//第二个线程开始尝试获取锁
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
compareAndSetState是一个本地方法CAS操作,线程1,线程2只会有一个成功,成功的继续执行lock后面的代码,失败的进入等待队列。
acquire创建node
创建队列之前,再次尝试是否能获取执行权力,看看线程1是否已经执行完毕了。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire-> nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
System.out.println("nonfairTryAcquire没有锁了哦:" + current.getId());
int c = getState();//getstate是volatile,
System.out.println("再次获取锁状态,看看有没有机会获取锁c:" + c);
if (c == 0) {
//state对线程2具有可见性,线程2拿到最新的state,再次判断一下能否持有锁(可能线程1同步代码执行得比较快,这会儿已经释放了锁),不可以就返回false。
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//判断当前是否是自己,如果是第一个线程再次调用了lock方法,nextc状态+1,这就是可重入了
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow//Integer.MAX_VALUE次,也就是2147483647,超过integer最大值会变负数
//这也说明了线程锁可重入的最大次数
throw new Error("Maximum lock count exceeded");
setState(nextc);//状态+1,并不会再次加锁,这就是可重入了。
return true;
}
return false;
}
进入等待队列之前再次cas一次,看看线程1是否已经执行完毕了,那线程2就可以直接执行了,而不用进入等待队列。
开始创建等待队列
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
System.out.println("addWaiter:创建等待队列:waitStatus" + node.waitStatus);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;//node节点指向最后一个节点
if (pred != null) {
System.out.println("tail不等于空哦:" + pred.thread.getId());
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
System.out.println("new node追加到队列尾巴");
return node;
}
}
System.out.println("队列为null,创建新的队列");
//创建一个新的队列
enq(node);
return node;
}
创建等待之前先判断是否已经有队列了,有则最佳到队尾。没有则开始enq
真正创建队列了
enq
private Node enq(final Node node) {
//这里使用了无限循环,配合下面的compareAndSet**方法的使用。
//compareAndSet**方法里面是一个CAS,如果设置失败了,就返回false,
//如果CAS返回false了,就在循环一直循环直到创建成功为止。
for (; ; ) {
System.out.println("无限循环中.......");
Node t = tail;
if (t == null) { // Must initialize
//如果没有节点,开始使用cas方式创建一个head节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
//node的上一个节点指向了t
node.prev = t;
//如果节点存在,则追加到队列的下一个节点。
System.out.println("队列终于不为null了");
if (compareAndSetTail(t, node)) {
//t的下一个队列指向了node;这是一个双向队列
t.next = node;
System.out.println("开始创建把节点追加到队列尾巴去,并返回节点");
return t;
}
}
}
}
等待队列acquireQueued
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
System.out.println("acquireQueued:新创建的等待节点,开始加入到队列里面了");
try {
boolean interrupted = false;
//无限循环,线程2被锁了,一直在等待线程1释放锁
for (; ; ) {
final Node p = node.predecessor();
//线程2尝试能否再次获取锁
if (p == head && tryAcquire(arg)) {
//线程2获取到了锁,线程2的node将成为队列头
System.out.println("获取锁后开始称为称为对头threadid:" + node.thread.getId());
setHead(node);
//丢去之前的队列头,这就是一个线程一个节点(第一个获取锁的线程不在队列里面),线程执行完毕后就把节点删除
p.next = null; // help GC
failed = false;
return interrupted;
}
//shouldParkAfterFailedAcquire设置线程等待,
//parkAndCheckInterrupt阻塞线程2停止往下执行;(这里是有2个线程,假设线程1先获取了锁,线程2执行lock时的内部操作)
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) {
interrupted = true;
System.out.println("加锁了,线程被挂起了。");
}
}
} finally {
if (failed)
cancelAcquire(node);
}
}
unlock取消锁
public final boolean release(int arg) {
//尝试解锁
//tryRelease只是把各种状态还原到初始化之前而已。
if (tryRelease(arg)) {
System.out.println("解锁成功,开始指向下一个节点,就是下一个线程获取锁的时候了");
Node h = head;//指向了线程2的node
//真正的解锁是在这里,线程1先获取了执行权,线程1可以执行;线程2才是被那个lock住的,所以这里开始唤醒线程2了。
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease开始尝试解锁
protected final boolean tryRelease(int releases) {
//getState()-1,和前面的setState对应,就是说线程1加了多少锁,就需要解多少锁。
int c = getState() - releases;
System.out.println("tryRelease开始解锁了c=" + c);
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
setStatus+1,加了几次也需要-1几次,直到为0时才为真正解锁。
tryRelease尝试成功后开始解锁 -> 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.
*/
//等待时被设置为-1了,
int ws = node.waitStatus;
System.out.println("unparkSuccessor-ws:" + ws);
//现在获得锁了,把自己的状态置为0
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;
}
//线程2不为空,解锁
if (s != null)
LockSupport.unpark(s.thread);
}
tryLock尝试获取锁
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;
}
//没有能获取到锁,直接返回false,不做任何操作。
return false;
}
tryLock的源码已经很清楚了,只是尝试看看自己能否获取锁,能获取就获取并返回true,不能获取就直接返回false,没有获取锁的时候并不会把线程加入到等待队列里面。所有tryLock只是在能获取锁的时候获取锁。
加锁解锁总结
1.加锁解锁过程是这样的
- 线程1执行lock时,获得了执行权,就开始执行了。
- 线程2执行lock时,发现获取不到执行权,就开始进入等待队列,并且把自己的当前线程2挂起。
- 线程1执行完后,调用unlock,unlock把线程2从加锁队列中解锁出来。
所以说加锁时把自己给锁住,解锁是有执行权限的线程去解救被锁住的线程。
2.从上面的代码也可以看出,AQS中每操作一步遇到线程竞争的过程都是采用CAS的无锁操作的(没有使用synchronized)