庄子与惠子游于濠梁之上。庄子曰:“儵鱼出游从容,是鱼之乐也。”惠子曰:“子非鱼,安知鱼之乐?”庄子曰:“子非我,安知我不知鱼之乐?”,你不是我,怎知我走过的路,遇过的事,吃过的苦,看过的春夏秋冬,你不是我,怎知我在热闹后孤单离去的背影,光鲜亮丽过后的尴尬,你不是我,怎知我笑容背后有没有忧伤,眼泪背后有没有万家灯火的照耀!
AbstractQueuedSynchronizer数据结构
AbstractQueuedSynchronizer
是一个双端队列,元素可以从队首进出,也可以从队尾进出,下面是AbstractQueuedSynchronizer
的成员变量。
abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
private static final long serialVersionUID = 7373984972572414691L;
protected AbstractQueuedSynchronizer() {}
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;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
}
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
}
复制代码
Node
prev: prev
是Node
的成员变量,代表队列中指向前一个节点的指针。
next: next
是指向下一个节点的指针。
thread: thread
是进入队列的线程。
SHARED:代表访问共享资源的线程被阻塞。
EXCLUSIVE: 代表访问独占资源的线程被阻塞。
waitStatus:代表线程的等待状态,CANCELLED
为线程被取消,SIGNAL
为线程需要唤醒,CONDITION
为在条件队列中等待, PROPAGATE
为节点释放时需要通知其他节点。
head , tail
head
为队列的头指针,tail
为队列的尾指针
state
state
是一个状态值,不同实现AbstractQueuedSynchronizer
的锁所代表的意思都不同,如果我们使用过CountDownLatch
,那么它的方法countDown()
就是执行的就是-1操作,操作的其实就是这个state
值,就相当于一个计数器,对于Semaphore
,CyclicBarrier
,他们也是通过操作 state
来进行计数,ReentrantLock
中,也是通过操作state
来实现线程是否获取到锁,当state
为0时,代表当前锁时空闲的,没有 被线程持有,如果state
为1,则当前锁被线程持有,如果大于1,则证明线程重入了,state+1,而对state的操作又分为独占和共享。
独占操作
独占的意思就是同一时间只有一个线程能操作,其他线程过来都会被阻塞,只有当前线程完成任务后释放了资源,其他线程才能继续获取资源,每一个线程都与资源进行绑定, 上面我们说的ReentrantLock
就是独占锁。
线程获取独占资源分析(以ReentrantLock为例)
acquire(arg)
首先进入acquire(arg)方法,然后做下一步的判断。
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
tryAcquire(arg)
tryAcquire(arg)判断资源是否被占用,在tryAcquire()中主要通过获取state
的值判断资源是否被占用,getState()
获取state
的值,如果为0,则证明资源没被 占用,就使用CAS操作更新值,然后state的值为1,然后使用setExclusiveOwnerThread()
设置独占资源的线程(独占资源和每一个线程进行绑定),然后返回,下面有一个 else if
判断,如果当前线程等于独占资源的线程,证明线程重入了,那么更新state
的值为2(state原本为1,重入后+1),最后返回。
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取state
int c = getState();
//资源没被独占
if (c == 0) {
//使用CAS对state进行更新
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//设置独占资源的线程
setExclusiveOwnerThread(current);
return true;
}
}
//如果线程等于服战资源的线程(重入)
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//state+1
setState(nextc);
return true;
}
return false;
}
复制代码
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
如果资源已经被独占了,那么就需要将当前线程插入AQS队列中,然后将当前线程挂起,我们看一下addWaiter(Node.EXCLUSIVE),可以看出将Node节点插入了AQS 的队尾。
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;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
复制代码
acquireQueued(final Node node, int arg)
acquireQueued
就是将当前线程挂起,我们看里面的一个方法parkAndCheckInterrupt()
, 可以看出使用 LockSupport.park(this)来将当前线程挂起。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
复制代码
release(int arg)
当一个线程调用了release(arg)方法释放资源时,它会调用tryRelease(arg)尝试释放资源。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
复制代码
tryRelease(int releases)
尝试释放资源的逻辑很简单,就是用AQS中的state
-1,如果为0,则能证明可以释放,就会通过setState(arg)
方法设置state
的值,注意,如果c=0,则 证明此线程不是重入的线程,那么就直接释放资源,并且解除与资源绑定的线程(setExclusiveOwnerThread(null)),如果c != 0 ,则证明资源被重入, 则将state - 1 ,不解除与独占资源绑定的线程。
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free
复制代码
释放完资源后,就会唤醒使用LockSupport.unpark(s.thread)唤醒AQS中的线程,然后又重复上面的过程。
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
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);
}
复制代码
共享操作(以CountDownLatch为例)
当线程进入countDownLatch.await()方式时,会调用AQS的acquireSharedInterruptibly
方法,然后进入tryAcquireShared(arg)
方法。
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
复制代码
tryAcquireShared(arg)
tryAcquireShared(arg)
的作用就是判断AQS状态值state
,如果state
= 0 , 返回1,否则返回-1,如果返回1,则证明线程对资源的操作已经结束,如果为-1,则证明对资源的操作 尚未结束,此时就会调用doAcquireSharedInterruptibly(arg)
方法。
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
复制代码
doAcquireSharedInterruptibly(arg)
调用doAcquireSharedInterruptibly(arg)
会通过addWaiter(Node.SHARED)
将当前线程封装成SHARED
类型的Node,然后插入AQS队列的尾部,然后调用 parkAndCheckInterrupt()
使用LockSupport.park(this)
将当前线程挂起。
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
复制代码
releaseShared(int arg)
当使用countDownLatch.countDown()时,其实就是对资源的释放,最终会调用releaseShared(int arg)方法,然后调用tryReleaseShared(int releases)`尝试释放资源
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
复制代码
tryReleaseShared(int releases)
调用tryReleaseShared(int releases)
尝试释放资源,释放资源的逻辑时根据state
来判断,如果state
= 0,证明当前资源没有任何线程持有,所以就直接返回,如果state
!= 0 , 证明资源还被线程持有,所以当前线程 就将state
- 1,然后继续向下执行doReleaseShared()
。
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
复制代码
doReleaseShared()
doReleaseShared()会调用unparkSuccessor(h)
来唤醒线程(LockSupport.unpark(s.thread)),因为传递过去的参数h = head , 所以可知唤醒的线程 是队头节点的线程。
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
复制代码
ConditionObject条件变量
ConditionObject
是AbstractQueuedSynchronizer
的内部类,他是实现线程间的同步的基础设施,它是与锁结合使用的(如ReentrantLock),ConditionObject 实现了Condition
接口,Condition
接口提供了await()
,signal()
等方法,实现线程的挂起和唤醒,ConditionObject
是一个 条件变量,每个条件变量对应一个条件队列,当调用Condition
的await()
被挂起的线程将会存放在条件队列中,调用signal()
时将从条件队列中移除并放入 AQS队列中。
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/**条件队列第一个节点 */
private transient Node firstWaiter;
/** 条件队列最后一个节点 */
private transient Node lastWaiter;
}
复制代码
示例
我们创建了一个ReentrantLock
锁,然后使用lock.newCondition()创建了一个条件变量,其实它最终创建的时AQS中的ConditionObject
条件变量,一个锁可以对应多个条件变量, 每个条件变量对应一个条件队列。
public class ConditionLockTest {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
System.out.println("start1");
condition.await();
System.out.println("start1-1");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}).start();
new Thread(() -> {
lock.lock();
try {
System.out.println("start2");
condition.signal();
System.out.println("start2-2");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}).start();
}
}
复制代码
从输出可以看出start1-1
在最后输出,第一个线程在输出start1
后就使用Condition
的await()
方法将线程挂起,第二个线程输出start2
后使用signal()
唤醒 线程1,然后输出start2-2
,最后输出线程1中的start1-1
,到这里,我们就知道AQS的内部类ConditionObject
的作用了。
关于AQS知识点,我们就分享到这里,AQS我们基本上不会直接去使用它,但是它对于我们来说却特别的重要,因为在JUC中,很多地方都是基于AQS来实现的,只有掌握了它, 我们在学习其他JUC的知识点的时候才会融会贯通,后面还会基于AQS实现一个自己的锁或者同步器,我是小四,我们下期见。