AQS源码解析2.内部核心结构与lock过程

有了上一节的手写迷你版的ReentrantLock,我们大致对AQS有了一些认识,接下来看一下AQS内部的一些结构。

1.AQS内部结构

1.1核心内部类Node

尝试获取锁失败的线程会被构造成一个Node节点去队列(双向链表结构)中排队,我们看一下Node的结构。

    static final class Node {
    
    
        
        //共享模式
        static final Node SHARED = new Node();
        
        //独占模式
        static final Node EXCLUSIVE = null;
		
        /*
         *  下面的这几个int类型的常量表示节点的状态值
         */
        
        //表示当前节点处于取消状态
        static final int CANCELLED =  1;
        
        /*
         *	表示当前节点需要唤醒它的后继节点,(signal表示的其实是后继节点的状态,需要当前节点去唤醒它。。。)
         */
        static final int SIGNAL    = -1;
        
        /* 
         * 等待队列(Condition)中的节点状态为 -2
         */
        static final int CONDITION = -2;
    	
        //ReentrantLock没有用到 后面将Condition队列时再讲
        static final int PROPAGATE = -3;
		
        
        /*
         * 表示node的状态,可选值(0, SIHGNAL(-1), CANCLLED(1),)  
         *  waitStatus = 0 默认状态
         *  waitStatus > 0 取消状态
         *  waitStatus = -1 表示当前Node如果是head节点时 释放锁之后需要唤醒后继节点
         */
        volatile int waitStatus;
		
        //Node的前驱节点
        volatile Node prev;
		
        //Node的后继节点
        volatile Node next;
        
        //Node内部封装的线程。
        volatile Thread thread;
        
        //ReentrantLock没有用到。
        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() {
    
        
        }
		
        Node(Thread thread, Node mode) {
    
         
            this.nextWaiter = mode;
            this.thread = thread;
        }
		
        Node(Thread thread, int waitStatus) {
    
     
            this.waitStatus = waitStatus;
            this.thread = thread;
        }     
    }

1.2核心属性

 	//队列头结点,任何时刻头结点对应的线程都是当前持锁线程。
	private transient volatile Node head;

    //队列尾节点
    private transient volatile Node tail;
	
    /*
     *  核心属性:表示资源
     *  独占模式下:0表示未加锁  >0表示加锁状态
     */
    private volatile int state;

	/*
	 * 继承父类的属性。
	 * 独占模式下表示当前持有锁的线程。
	 */
	private transient Thread exclusiveOwnerThread;

2.Lock过程(公平锁为例)

2.1详细流程图

尝试获取锁+构造节点入队过程

在这里插入图片描述

在队列中被挂起+被唤醒重新抢锁的过程

在这里插入图片描述

2.2AQS.acquire()

 	//AQS.acquire()
	public final void acquire(int arg) {
    
    
        
        /*
         *  1.tryAcquire()尝试获取锁,获取成功返回true,获取失败返回false。
         *  2.
         *   	2.1 addWaiter()    获取锁失败将当前线程封装成一个Node入队
         *  	2.2 acquireQueued(),在队列中尝试获取锁的过程,返回值表示挂起过程中线程
         * 	                         是否是被中断唤醒的,false表示不是被中断唤醒的。 	
         */
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //设置中断标志位为true。
            selfInterrupt();
    }

2.3ReentrantLock.tryAcquire()

        protected final boolean tryAcquire(int acquires) {
    
    
            //当前线程
            final Thread current = Thread.currentThread();
            //获取state的值(即当前锁的状态)
            int c = getState();
            
            //c == 0表示当前处于无锁状态。
            if (c == 0) {
    
    
                
                /*
                 *  因为这里是公平锁,所以必须先查看队列中是否有节点,有节点就得去排队,不允许竞争锁。true表示队列中有节点 
                 *  false表示队列中没有节点
                 *  hasQueuedPredecessors()查看队列中是否有节点。
                 *   
                 *  只有当队列中没有节点时才能使用CAS操作去获取锁,
                 */
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
    
    
                    
                    //抢锁成功 将当前线程设置为获取锁的线程
                    setExclusiveOwnerThread(current);
                    //直接return true。
                    return true;
                }
            }
            
            /*
             * 走到这里就说明当前锁已经被占用了,因为ReentrantLock是可重入锁,所以会判断
             * 持有锁的线程是否就是当前线程,即这里是重入的逻辑。
             */
            else if (current == getExclusiveOwnerThread()) {
    
    
                //重入的逻辑
                int nextc = c + acquires;
                //这里判断 nextc < 0,是判断是否超出了int最大值。
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                
                //设置新的state,setState是一个无锁方法,因为只有持锁线程才能进到这里
                setState(nextc);
                //重入锁再次加锁成功 也返回true。
                return true;
            }
            
            /*
             *  什么时候返回false。
             *  1. state = 0,无锁状态,但是CAS抢锁失败(有并发)
             *  2. state > 0, 且持锁线程非当前线程。
             */
            return false;
        }
    }

2.4AQS.addWaiter()

  	 //最终会返回当前节点
	 private Node addWaiter(Node mode) {
    
    
      
      	//将当前线程封装成一个Node节点,mode是独占模式(ReentrantLock)
        Node node = new Node(Thread.currentThread(), mode);
        
        //获取尾节点
        Node pred = tail;
      	
        
        if (pred != null) {
    
    
            //将当前节点的前驱指向pred
            node.prev = pred;		
            //CAS设置tail指向node (这里可能有并发,所以要使用CAS操作)
            if (compareAndSetTail(pred, node)) {
    
    
                //CAS成功,将尾结点的后继指向node
                pred.next = node;
                //说明入队成功,返回node即可。
                return node;
            }
        }
        
      	/*
      	 * 什么时候会执行到这里?
      	 *  1. 当前队列是空队列,tail == null
      	 *  2. CAS竞争入队失败。。会来到这里
      	 *   
      	 *   enq()方法,不断自旋入队。
      	 */  
        enq(node);
        return node;
    }

2.5AQS.enq()

    private Node enq(final Node node) {
    
    
        //自旋,保证一定可以入队,只有入队才会跳出循环
        for (;;) {
    
    
            Node t = tail;
  			
            /*
             *  当前队列是空队列,tail == null
             *  说明当前锁被占用,但是队列没有节点,说明当前线程可能是第一个获取锁失败的线程(存在并发)
             * ,那么作为当前持锁线程的第一个后继线程,需要做什么?
             *   
             *  因为第一个当前持锁的线程,获取锁时直接tryAcquire成功了,没有向阻塞队列中条件任何的node,
             *  所以作为后继需要为它补一个Node
             */
            if (t == null) {
    
     // Must initialize
                //CAS向队列中追加一个Node 然后继续自旋
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
    
    
                //CAS入队。
                node.prev = t;
                if (compareAndSetTail(t, node)) {
    
    
                    t.next = node;
                    return t;
                }
            }
        }
    }

2.6AQS.acquireQueued()

    /*
     * acquireQueued()方法的作用?
 	 *  1.当前节点入队后有没有被挂起呢? 
	 *  2.如果被挂起了? 那么唤醒之后的逻辑在哪?
	 *  都在此方法中。
	 *   
	 *  @param node 表示当前封装的node
	 *  @param arg  表示加锁的arg参数 
     */
	final boolean acquireQueued(final Node node, int arg) {
    
    
        
        /*
         *  true 表示当前线程抢占锁成功 (一般情况下,最终肯定会获取到锁)
         *  false 表示失败,需要执行出队的逻辑(后面将响应中断的lock方法时再讲)
         */
        boolean failed = true;
        try {
    
    
            //表示当前线程是否被中断。
            boolean interrupted = false;
            
            //自旋 获取锁
            for (;;) {
    
    
                
                /*
                 *  什么情况会执行这里?
                 *   1.进入for循环时,在线程尚未park前
                 *   2.线程park后,被唤醒后,也会执行这里(自旋)
                 */
                
                //拿到节点的前驱节点
                final Node p = node.predecessor();
                
                /*
                 *  这里判断当前节点是不是head.next节点
                 *  条件成立的话才有机会调用tryAcquire()尝试获取锁
                 */
                if (p == head && tryAcquire(arg)) {
    
    
                    //拿到锁之后,设置头结点为当前节点,将前驱节点出队。
                    setHead(node);
                    p.next = null; // help GC
                    //没有发生异常 
                    failed = false;
                    //返回当前线程的中断标记。
                    return interrupted;
                }
                
                /*
                 * shouldParkAfterFaildAcquire():当前线程获取锁失败后,是否需要挂起? 
                 *  true -> 需要挂起  | false -> 不需要挂起
                 * -------
                 * parkAndCheckInterrupt()将当前线程挂起,被唤醒后返回中断标志位(会清除中断标志位)
                 *   返回当前线程是否是被中断唤醒的。
                 *  
                 *  (唤醒 :1.正常唤醒 其他线程unpark 2.其他线程给当前挂起的线程一个中断信号)
                 */
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //如果当前线程是被中断唤醒的,就将interrupted置为ture。
                    interrupted = true;
            }
        } finally {
    
    
            if (failed)
                //取消竞争。
                cancelAcquire(node);
        }
    }

//---------------------------------------------------------------------------- 
	private final boolean parkAndCheckInterrupt() {
    
    
        //使用LockSupport挂起当前线程
        LockSupport.park(this);
        //返回当前线程是否被中断,该方法会清除线程的中断标志,
        return Thread.interrupted();
     }  

2.7AQS.shouldParkAfterFailedAcquire()

总结:

  • 1.当前节点的前驱节点是cancel状态,第一次来到方法时,会将前面所有处于cancel的节点全部删除,最终找到第一个状态是0的节点,然后将其状态设置为**-1(SIGNAL)**,然后继续自旋后返回true。
  • 2.当前节点的前驱节点状态是0,当前线程会设置前驱节点的状态为-1,然后再次自旋时会返回true。

主要做的事就是删除当前节点前面连续的所有处于cancel的节点,找到第一个状态为0的节点,将其状态设置为-1(SIGNAL).然后退出。

		/*
		 *  @param pred 当前线程node的前置节点
		 *  @param node 当前线程对应的node
		 *  @return true -> 当前线程需要挂起 false -> 当前线程不需要挂起
		 */  
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    
    
        /*
         *  获取当前节点前驱节点的状态(waitStatus).
         *   waitStatus = 0 初始默认状态
         *   			> 0 表示节点时cancel取消状态
         * 				-1  (SIGNAL)表示当前节点释放锁后会唤醒它的第一个后继节点。
         */
    	int ws = pred.waitStatus;
    	//表示当前节点的前驱节点状态就是SIGNAL,直接return true。
        if (ws == Node.SIGNAL)
            return true;
    	
    	/*
    	 *  ws > 0 表示当前节点node的前驱节点的waitStatus > 0是取消状态,取消状态的
    	 *  节点无法唤醒后继节点,所以需要一直向前找到第一个waitStatus <= 的节点。
    	 *  在这个过程中,会将waitStatus > 0的节点全部删除。
    	 */
        if (ws > 0) {
    
    
            do {
    
    
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            //找到的第一个ws <= 0的节点的next指针指向当前node。
            pred.next = node;
        } else {
    
    
            // 到这里 说明 ws = 0,
            //将当前线程node的前置节点强制设置为SIGNALE,表示前置节点释放锁之后需要唤醒我
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

猜你喜欢

转载自blog.csdn.net/qq_46312987/article/details/121685203
今日推荐