前言:
自旋锁:
在访问共享变量时,如果访问资源的线程没有拿到访问权限(未拿到锁),不是让线程释放资源,而是让线程处于无限循环的请求锁,并一直检测自己的状态,只有当请求到锁时才会继续下面的执行,否则就会一直处于阻塞状态
缺点:
- 可能导致死锁
- 占用cpu
优化 :
- 将杂乱无章的线程通过队列(CLH队列)来管理
- 将自旋过程中对共享变量的访问来获取同步状态的过程转化为了对前驱节点状态的访问
AbstractQuenedSynchronizer
- ReentrantLock
- ReentrantReadWriteLock
- CountDownLatch
- Semaphone
重点:
AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch) 在第四章有代码介绍。
Node的状态
- CANCELLED:值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化。
- SIGNAL:值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行。说白了,就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。
- CONDITION:值为-2,与Condition相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
- PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态。
0状态:值为0,代表初始化状态。
方法介绍
- isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
- tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
- tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
- tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
- tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
互斥模式(exclusive)
acquire
public final void acquire(int arg) {
//如果tryacquire失败 且 队列里获取节点成功 且被中断过
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 如果在acquireQueued过程线程被interrupt,如果线程没进入等待状态,并不会中断线程。只是改变interrupt变量,且被传回到这里(因为是用interrupted,所以返回true之后又把线程的interrupt变量设为false)。然后selfinterrupt,将interrupt变量设为true
- 如果线程被park了然后被interrupt,则被唤醒,循环一次发现还是阻塞又进入park等待状态。直到被unpark,interrupt参数为true传回到这里.然后interrupt参数又变回false(受interrupted影响),selfinterrupt则又把它设为true。
private Node addWaiter(Node mode) {
// mode表示该节点的共享/排他性,null为排他,不为null为共享
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;
}
private Node enq(final Node node) {
// 自旋
for (;;) {
Node t = tail; //
if (t == null) { // 如果尾部节点不存在的时候,创建节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
总结: 通过AQS源码分析我们知道,如果当前线程所在的队列没有其他线程在队列中等待,那我们就知道插入,否则就自旋。
java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
阻塞判断的方法:
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;
}
// 该节点不是头节点或者获取失败 进入.............
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
/**
* 这个分支是遇到了未知的错误,或者异常,此时的failed就是true,
* 不能正常的设置成false,那么就要将当前节点设置成取消的状态
*/
if (failed)
cancelAcquire(node);
}
}
该节点不是头节点或者获取失败 进入…
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.
*/
return true;
//这个状态表示,前置节点是取消的状态,那就将指针一直往前找,找到第一个不大于0的
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.
*/
//将前置节点设置成SIGNAL,当然CAS可能是失败的,那就进入外层的死循环
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
中断
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#cancelAcquire 取消节点状态
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
// 判断当前节点是否存在
if (node == null)
return;
// 取消当前node对象的线程
node.thread = null;
// Skip cancelled predecessors
// 获取前一个节点
Node pred = node.prev;
// 如果当前节点的前置节点都是取消状态的,那么就一直找到非取消状态的前置节点
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
// 拿到前置节点的后置节点,其实就是当前节点,不过为了后面的设置有用
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
// waitStatus是int的,能够原子的副职成取消状态,
//这样一赋值,后面的再次添加到队列中的结点就能忽略当前这个取消的结点了
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
// 如果当前取消的结点是尾节点的话,就直接将尾节点设置成前置节点,这样当前节点就解链了
if (node == tail && compareAndSetTail(node, pred)) {
// 将原先前置节点的后置节点置为空,这样当前节点就是一个可gc的对象了
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
/**
* 这个分支稍有复杂:
* 走到这个分支,首先取消的结点并不是尾节点;
* 另外如果此前置节点也不是头结点,并且是后置节点等待资源的状态(SIGNAL),
* 那么将当前取消节点的后置节点,提前;
* 如果前置节点是头节点或者前置节点已经是取消的状态、或者前置节点设置状态位失败、或者
* 前置节点的内部线程是一个空,那么,
* 将当前取消节点的后置节点唤醒去争取资源(unparkSuccessor)
*/
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
release解析
java.util.concurrent.locks.AbstractQueuedSynchronizer#release
public final boolean release(int arg) {
//tryRelease具体同步工具实现的方法
if (tryRelease(arg)) {
Node h = head;
// 头结点如果是0的状态,表示当前线程获取资源是初始状态,直接返回true就可以了
if (h != null && h.waitStatus != 0)
// 这个方法也是分析过的,这里就是将头节点的下面一个节点进行唤醒操作
unparkSuccessor(h);
return true;
}
return false;
}
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
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);
}
共享锁分析
共享锁的入口
public final void acquireShared(int arg) {
// tryAcquireShared同步工具类的实现方法,小于0表示没有请求到足够的资源
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
// 和独占锁类似,也是创建一个共享锁的结点,加入到队尾
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) { // 自旋
// 获取当前节点的前一个节点
final Node p = node.predecessor();
// 如果为头及诶单
if (p == head) {
// 是否能获取到资源
int r = tryAcquireShared(arg);
//能获取到
if (r >= 0) {
// 这种是获取到足够资源的情况下,对后继节点进行一系列的逻辑处理
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
// 正常获取到资源的返回出口
return;
}
}
// 下面和前面的独占锁一样,不在多说
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
// 将当前节点设置成头节点
setHead(node);
/**
* 这里就是表示,如果资源足够,或者头节点为空,或者头节点为SIGNAL或者PROPAGATE状态
* 那么要试着去释放头节点后面的结点,去试着竞争资源
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
private void doReleaseShared() {
/**
* 确保这种释放得以传播,即使有其他正在进行的获取或者释放资源的操作。
* 通常情况,如果head的状态是SIGNAL,它会试图唤醒head的后继者。
* 但是如果没有,则将状态设置为PROPAGATE,以确保在释放资源时,能够继续通知后继的节点。
* 此外,我们必须循环,以防在执行此操作时添加新节点。
* 另外,与唤醒后继节点的其他使用不同,
* 我们需要知道ca是否重置状态失败,如果失败,则需要重新检查。
*/
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;
}
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
https://my.oschina.net/UBW/blog/2995774
https://www.cnblogs.com/llsblog/p/10629784.html