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源码解析

jdk1.8 J.U.C并发源码阅读------CyclicBarrier源码解析

jdk1.8 J.U.C并发源码阅读------CyclicBarrier源码解析