jdk1.8 J.U.C并发源码阅读------AQS之共享锁的获取与释放
Posted Itzel_yuki
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jdk1.8 J.U.C并发源码阅读------AQS之共享锁的获取与释放相关的知识,希望对你有一定的参考价值。
一、继承关系
since1.5
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable
继承自AbstractQueuedSynchronizer抽象类。
二、成员变量
private transient volatile Node head;//CLH队列首节点
private transient volatile Node tail;//CLH队列尾节点
private volatile int state;//锁的占用次数
三、内部类
CLH队列(链表结构的队列)的node的数据结构:
static final class Node
//标志Node的状态:独占状态
static final Node SHARED = new Node();
//共享状态
static final Node EXCLUSIVE = null;
//因为超时或者中断,node会被设置成取消状态,被取消的节点时不会参与到竞争中的,会一直保持取消状态不会转变为其他状态;
static final int CANCELLED = 1;
//该节点的后继节点被阻塞,当前节点释放锁或者取消的时候(cancelAcquire)需要唤醒后继者。
static final int SIGNAL = -1;
//CONDITION队列中的状态,CLH队列中节点没有该状态,当将一个node从CONDITION队列中transfer到CLH队列中时,状态由CONDITION转换成0
static final int CONDITION = -2;
//该状态表示下一次节点如果是Shared的,则无条件获取锁。
static final int PROPAGATE = -3;
//当一个新的node在CLH队列中被创建时初始化为0,在CONDITION队列中创建时被初始化为CONDITION状态
volatile int waitStatus;
//队列中的前驱节点
volatile Node prev;
//next连接指向后继节点,当前节点释放锁时,需要唤醒它后面第一个非cancelled状态的节点。
//当一个节点入队时,直接添加到队尾,在acquireQueue中调用shouldParkAfterFailedAcqurie时修改前一个节点的状态,若前一个节点是cancelled则直接删除前驱节点,调整prev指向非cancelled的前驱;若前驱节点的状态是0,-3时,调整状态为signal
//在enq方法中,只有当成功的将tail指针指向新的尾节点时,才给之前的尾节点设置next的值,因此,当一个节点的next指针是null时,并不意味着该节点是尾节点。
//cancelled状态的node的next指向该节点自身而不是null。
volatile Node next;
//当前线程
volatile Thread thread;
//CONDITION队列中指向下一个node的指针,CLH队列中不使用
Node nextWaiter;
final boolean isShared()
return nextWaiter == SHARED;
//返回前驱节点
final Node predecessor() throws NullPointerException
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
Node() // Used to establish initial head or SHARED marker
Node(Thread thread, Node mode) // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
Node(Thread thread, int waitStatus) // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
四、方法说明
基本过程总结: 共享状态获取锁和释放锁代码分析:获取共享锁的基本过程:
(1)调用tryAcquireShared获取锁,成功返回值大于等于0,失败小于0。如果失败则调用doAcquireShared方法获取锁。
(2)将当前进程作为一个node加入到CLH队列中,当前线程进入for循环查看node的前驱是否是队列的首节点,
如果是,则再次调用tryAcquireShared尝试获取锁,
获取成功则调用setHeadAndPropagate,将当前节点设置为首节点,同时若tryAcquireShared的返回值大于0(锁的个数)(不等于0),或者原先首节点的waitStatus小于0,则查看node的next节点是否为shared状态,若next节点为null或者为shared状态,则调用doReleaseShared进行后继操作(可能是唤醒后继的操作),具体是,当前节点的waitStatus为signal,则用CAS操作设置当前节点的状态为0,同时unparkSuccessor唤醒后继节点;若当前节点的状态为0(后继为null),则调用CAS操作将状态改成PROPAGATE。
否则,则调用shouldParkAfterAcquireFailed查看当前节点是否应该被挂起,若前一个节点的状态为signal则应该被挂起,若为cancelled,则删除node前面所有连续的状态为cancelled的节点,找到第一个状态非cancelled的节点,将node的prev指向它,返回false,不应该被挂起,应该重新判断前一个节点的状态;其余情况,等于0或者为PROPAGATE,则CAS将状态改成signal,返回false,重新判断节点状态。
共享锁获取总结:
若共享锁是
shared--->shared--->shared--->exclusive。
signal--->signal--->signal--->0
则第一个node获取锁后,将无条件的唤醒第二个,同时更新头节点,第二个无条件唤醒第三个,同时更新头结点,第三个在进行setHeadAndPropagate时,在setHead之后,由于第四个节点是一个独占锁,所以不进行doReleaseShared操作了。共享锁能无条件唤醒后继相邻的共享锁,但是一遇到一个独占锁,这种无条件唤醒就结束了。
shared-->shared
signal-->0
PROPAGATE
若第一个共享节点唤醒了第二个共享节点,第二个节点设置了head后发现没有后继,所以需要进行doReleaseShared操作,该操作有两个作用,如果有后继,则唤醒后继;如果没有后继,则将该共享节点的状态改成PROPAGATE,表示一旦有一个共享节点连接在该节点后,该节点的共享锁将无条件传播给下一个共享节点。
PROPAGATE:只可能出现在队首,并且将共享节点的状态又0修改成PROPAGATE时,该队列中只有该节点,所以PROPAGATE的作用就是通知下一个连接在该节点后面的共享节点,共享锁将无条件传播。
代码分析: (1)acquireShared:共享锁的获取
public final void acquireShared(int arg)
//tryAcquireShared,模板方法,子类实现。调用tryAcquireShared尝试获取锁
if (tryAcquireShared(arg) < 0)
//获取失败则调用doAcquireShared获取锁
doAcquireShared(arg);
private void doAcquireShared(int arg)
//以共享状态创建该线程的节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try
boolean interrupted = false;
//循环
for (;;)
//前驱节点p
final Node p = node.predecessor();
//p是首节点
if (p == head)
//尝试获取锁,r大于等于0表示获取成功
int r = tryAcquireShared(arg);
//获取成功
if (r >= 0)
//重新设置首节点,并且向后唤醒shared状态的节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
//p不是首节点,查看是否应该阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
finally
//获取失败,取消节点
if (failed)
cancelAcquire(node);
//设置当前共享状态的node为head,同时,如果下一个节点是share并且waitStatus为signal或者propagate则释放当前锁,进行锁释放后的准备(doReleaseShared)
private void setHeadAndPropagate(Node node, int propagate)
Node h = head; // Record old head for check below
//设置新的首节点
setHead(node);
//propagate>=0
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0)
Node s = node.next;
//当后继节点s是一个shared状态的节点,或者s没有后继节点,则当前节点释放锁,同时处理后事
if (s == null || s.isShared())
doReleaseShared();
private void doReleaseShared()
for (;;)
Node h = head;
//队列不为Null
if (h != null && h != tail)
int ws = h.waitStatus;
//首节点是signal,则直接CAS设置状态,同时调用unparkSuccessor唤醒后继节点
if (ws == Node.SIGNAL)
//首节点状态设置成0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//并且唤醒h的后继节点
unparkSuccessor(h);
//首节点的状态是0,则通过CAS设置为PROPAGATE,表示如果新连接在当前节点后的node的状态如果是shared,则无条件获取锁。
//考虑这样一种情况,当前节点node是一个shared状态的节点,并且是tail,则当前节点node的状态为0。当node成功获取锁后,调用setHeadAndPropagate设置当前节点为head后,(此时node的前一个节点waitstatus<0,可以进入外层的if语句,next为空,则可以进入内层if语句)调用doReleaseShared进行释放锁后的处理,此时当前节点就可以通过下面代码将状态设置为PROPAGATE了。若此后有一个新的shared状态的node添加到队尾,则可以直接获取到锁,再次进入setHeadAndPropagate时,当前节点的waitStatus为PROPAGATE(propagate可能等于0),保证能进入外层的if语句。
//PROPAGATE,表示下一次(当前节点获得锁时下一个节点还没有添加到队列中),shared状态节点将会无条件传播共享状态。
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
if (h == head) // loop if head changed
break;
(2)releaseShared:共享锁的释放
public final boolean releaseShared(int arg)
//tryReleaseShared释放成功,则调用doReleaseShared进行后继处理
if (tryReleaseShared(arg))
doReleaseShared();
return true;
return false;
//当该节点是队列中唯一一个节点时,状态为0,直接通过CAS操作将状态改成PROPAGATE(之所以要修改而不保持为0是因为,若下一个节点是shared的节点,则可以直接获取锁。当下一个节点获取锁成功,调用setHeadAndPropagate,该方法中第一个if可能propagate为0(N),但是waitStatus状态小于0(Y),则还是可以直接进入外层if中),否则,该节点状态为SIGNAL,CAS修改状态为0,之后调用unparkSuccessor唤醒后继节点(独占的和共享的节点都要唤醒)。
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;
以上是关于jdk1.8 J.U.C并发源码阅读------AQS之共享锁的获取与释放的主要内容,如果未能解决你的问题,请参考以下文章
jdk1.8 J.U.C并发源码阅读------ReentrantLock源码解析
jdk1.8 J.U.C并发源码阅读------ReentrantLock源码解析
jdk1.8 J.U.C并发源码阅读------CountDownLatch源码解析
jdk1.8 J.U.C并发源码阅读------CountDownLatch源码解析