JAVA并发-同步器AQS

什么是AQS

aqs全称为AbstractQueuedSynchronizer,它提供了一个FIFO队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLock、CountDownLatch等。
AQS是一个抽象类,主要是通过继承的方式来使用,它本身没有实现任何的同步接口,仅仅是定义了同步状态的获取以及释放的方法来提供自定义的同步组件。
可以这么说,只要搞懂了AQS,那么J.U.C中绝大部分的api都能轻松掌握。

AQS的两种功能

从使用层面来说,AQS的功能分为两种:独占和共享

  • 独占锁,每次只能有一个线程持有锁,比如前面给大家演示的ReentrantLock就是以独占方式实现的互斥锁
  • 共享锁,允许多个线程同时获取锁,并发访问共享资源,比如ReentrantReadWriteLock

简单认识AQS

何为AQS?

1、AQS是一个抽象类,类名为AbstractQueuedSynchronizer,抽象的都是一些公用的方法属性,其自身是没有实现任何同步接口的;

2、AQS定义了同步器中获取锁和释放锁,目的来让自定义同步器组件来使用或重写;

3、纵观AQS的子类,绝大多数都是一个叫Sync的静态内部类来继承AQS类,通过重写AQS中的一些方法来实现自定义同步器;

4、AQS定义了两种资源共享方式:EXCLUSIVE( 独占式:每次仅有一个Thread能执行 )、SHARED( 共享式:多个线程可同时执行 );

5、AQS维护了一个FIFO的CLH链表队列,且该队列不支持基于优先级的同步策略;

AQS的state

1、private volatile int state:维护了一个volatile的int类型的state字段,该字段是实现AQS的核心关键词; 

2、通过getState、setState、compareAndSetState方法类获取、设置更新state值;

3、该字段在不同的并发类中起着不同的纽带作用,下面会接着讲到state字段的一些应用场景;

Node的waitStatus

当前Node的等待状态

  1. 初始值整数0:当前节点如果不指定初始化状态值,默认值就是0,侧面说明节点正在等待队列中处于等待状态。
  2. Node#CANCELLED整数值1:表示当前节点实例因为超时或者线程中断而被取消,等待中的节点永远不会处于此状态,被取消的节点中的线程实例不会阻塞。
  3. Node#SIGNAL整数值-1:表示当前节点的后继节点是(或即将是)阻塞的(通过park),当它释放或取消时,当前节点必须unpark它的后继节点。
  4. Node#CONDITION整数值-2:表示当前节点是条件队列中的一个节点,当它转换为同步队列中的节点的时候,状态会被重新设置为0。
  5. Node#PROPAGATE整数值-3:此状态值通常只设置到调用了doReleaseShared()方法的头节点,确保releaseShared()方法的调用可以传播到其他的所有节点,简单理解就是共享模式下节点释放的传递标记。
对于释放操作的时候,前一个结点有唤醒后一个结点的任务;
当前结点的前置结点waitStatus > 0,则结点处于CANCELLED状态,应该需要踢出队列;
当前结点的前置结点waitStatus = 0,则需要将前置结点改为SIGNAL状态;

CLH队列

1、队列模型:
      +------+  prev +------+  prev +------+
      |      | <---- |      | <---- |      |  
 head | Node |  next | Node |  next | Node |  tail
      |      | ----> |      | ----> |      |  
      +------+       +------+       +------+

2、链表结构,在头尾结点中,需要特别指出的是头结点是一个空对象结点,无任何意义,即傀儡结点;
      
3、每一个Node结点都维护了一个指向前驱的指针和指向后驱的指针,结点与结点之间相互关联构成链表;

4、入队在尾,出队在头,出队后需要激活该出队结点的后继结点,若后继结点为空或后继结点waitStatus>0,则从队尾向前遍历取waitStatus<0的触发阻塞唤醒;

state在AQS简单应用举例

1、CountDownLatch,简单大致意思为:A组线程等待另外B组线程,B组线程执行完了,A组线程才可以执行;
   state初始化假设为N,后续每countDown()一次,state会CAS减1。
   等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

2、ReentrantLock,简单大致意思为:独占式锁的类;
   state初始化为0,表示未锁定状态,然后每lock()时调用tryAcquire()使state加1,
   其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁;

3、Semaphore,简单大致意思为:A、B、C、D线程同时争抢资源,目前卡槽大小为2,若A、B正在执行且未执行完,那么C、D线程在门外等着,一旦A、B有1个执行完了,那么C、D就会竞争看谁先执行;
   state初始值假设为N,后续每tryAcquire()一次,state会CAS减1,当state为0时其它线程处于等待状态,
   直到state>0且<N后,进程又可以获取到锁进行各自操作了;

常用重要的方法

1、protected boolean isHeldExclusively()
   // 需要被子类实现的方法,调用该方法的线程是否持有独占锁,一般用到了condition的时候才需要实现此方法

2、protected boolean tryAcquire(int arg)
   // 需要被子类实现的方法,独占方式尝试获取锁,获取锁成功后返回true,获取锁失败后返回false

3、protected boolean tryRelease(int arg)  
   // 需要被子类实现的方法,独占方式尝试释放锁,释放锁成功后返回true,释放锁失败后返回false
   
4、protected int tryAcquireShared(int arg)  
   // 需要被子类实现的方法,共享方式尝试获取锁,获取锁成功后返回正数1,获取锁失败后返回负数-1
   
5、protected boolean tryReleaseShared(int arg)   
   // 需要被子类实现的方法,共享方式尝试释放锁,释放锁成功后返回正数1,释放锁失败后返回负数-1
   
6、final boolean acquireQueued(final Node node, int arg)
   // 对于进入队尾的结点,检测自己可以休息了,如果可以修改则进入SIGNAL状态且进入park()阻塞状态

7、private Node addWaiter(Node mode)
   // 添加结点到链表队尾

8、private Node enq(final Node node)
   // 如果addWaiter尝试添加队尾失败,则再次调用enq此方法自旋将结点加入队尾

9、private static boolean shouldParkAfterFailedAcquire(Node pred, Node node)
   // 检测结点状态,如果可以休息的话则设置waitStatus=SIGNAL并调用LockSupport.park休息;

10、private void unparkSuccessor(Node node)   
   // 释放锁时,该方法需要负责唤醒后继节点

前提源码

AQS内部类Node

AQS 内部提供了一个内部类.用来作为同步队列和等待队列的节点对象.
不同队列的节点.其使用的属性和含义是不同的

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;


    //是否时CLH队列的节点同时时共享式获取同步状态
    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传入Node.SHARED或Node.EXCLUSIVE
    Node(Thread thread, Node mode) {
        this.nextWaiter = mode;
        this.thread = thread;
    }

    //创建等待队列节点,waitStatus传入Node.CONDITION
    Node(Thread thread, int waitStatus) {
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

具体执行源码解读

ReentrantLock构造器

1、构造器源码:
    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    
2、默认构造方法为非公平锁,带参构造方法还可通过传入变量还决定调用方是使用公平锁还是非公平锁;    

Sync同步器

1、AQS --> Sync ---> FairSync // 公平锁
                  |
                  |> NonfairSync // 非公平锁
                  
2、ReentrantLock内的同步器都是通过Sync抽象接口来操作调用关系的,细看会发现基本上都是通过sync.xxx之类的这种调用方式的;

lock()

1、源码:
    public void lock() {
        sync.lock();
    }
    
    // FairSync 公平锁调用方式
    final void lock() {
        acquire(1); // 尝试获取独占锁
    }    
    
    // NonfairSync 非公平锁调用方式
    final void lock() {
        if (compareAndSetState(0, 1)) // 首先判断state资源是否为0,如果恰巧为0则表明目前没有线程占用锁,则利用CAS占有锁
            setExclusiveOwnerThread(Thread.currentThread()); // 当独占锁之后则将设置exclusiveOwnerThread为当前线程
        else
            acquire(1); // 若CAS占用锁失败的话,则再尝试获取独占锁
    }
    
2、这里的区别就是非公平锁在调用lock时首先检测了是否通过CAS获取锁,发现锁一旦空着的话,则抢先一步占为己有,
   不管有没有阻塞队列,只要当前线程来的时候发现state资源没被占用那么当前线程就抢先一步试一下CAS,CAS失败了它才去排队;

acquire(int)

1、源码:
    public final void acquire(int arg) {
        if (!tryAcquire(arg) && // 尝试获取锁资源,若获取到资源的话则线程直接返回,此方法由AQS的具体子类实现
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 否则获取资源失败的话,那么就进入等待队列
            selfInterrupt();
    }
    
2、该方法是独占模式下线程获取state共享资源的入口,如果获取到资源的话就返回,否则创建独占模式结点加入阻塞队列,直到获取到共享资源;

3、而且这里需要加上自我中断判断,主要是因为线程在等待过程中被中断的话,它是不响应的,那么就只有等到线程获取到资源后通过自我判断将这个判断后续补上;

4、独占模式的该方法,正常情况下只要没有获取到锁,该方法一直处于阻塞状态,获取到了则跳出该方法区;

tryAcquire(int)

1、公平锁tryAcquire源码:
    // FairSync 公平锁的 tryAcquire 方法
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState(); // 获取锁资源的最新内存值
        if (c == 0) { // 当state=0,说明锁资源目前还没有被任何线程被占用
            if (!hasQueuedPredecessors() && // 检查线程是否有阻塞队列
                compareAndSetState(0, acquires)) { // 如果没有阻塞队列,则通过CAS操作获取锁资源
                setExclusiveOwnerThread(current); // 没有阻塞队列,且CAS又成功获取锁资源,则设置独占线程对象为当前线程
                return true; // 返回标志,告诉上层该线程已经获取到了锁资源
            }
        }
        // 执行到此,锁资源值不为0,说明已经有线程正在占用这锁资源
        else if (current == getExclusiveOwnerThread()) { // 既然锁已经被占用,则看看占用锁的线程是不是当前线程
            int nextc = c + acquires; // 如果占用的锁的线程是当前线程的话,则为重入锁概念,状态值做加1操作
            // int类型值小于0,是因为该int类型的state状态值溢出了,溢出了的话那得说明这个锁有多难获取啊,可能出问题了
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true; // 返回成功标志,告诉上层该线程已经获取到了锁资源
        }
        return false; // 返回失败标志,告诉上层该线程没有获取到锁资源
    }

2、非公平锁tryAcquire源码:
    // NonfairSync 非公平锁的 tryAcquire 方法
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires); // 调用父类的非公平获取锁资源方法
    }    

    // NonfairSync 非公平锁父类 Sync 类的 nonfairTryAcquire 方法    
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState(); // 获取锁资源的最新内存值
        if (c == 0) { // 当state=0,说明锁资源目前还没有被任何线程被占用
            if (compareAndSetState(0, acquires)) { // 先不管三七二十一,先尝试通过CAS操作获取锁资源
                setExclusiveOwnerThread(current); // CAS一旦成功获取锁资源,则设置独占线程对象为当前线程
                return true;// 返回成功标志,告诉上层该线程已经获取到了锁资源
            }
        }
        // 执行到此,锁资源值不为0,说明已经有线程正在占用这锁资源
        else if (current == getExclusiveOwnerThread()) { // 既然锁已经被占用,则看看占用锁的线程是不是当前线程
            int nextc = c + acquires; // 如果占用的锁的线程是当前线程的话,则为重入锁概念,状态值做加1操作
            // int类型值小于0,是因为该int类型的state状态值溢出了,溢出了的话那得说明这个锁有多难获取啊,可能出问题了
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc); // 
            return true; // 返回成功标志,告诉上层该线程已经获取到了锁资源
        }
        return false; // 返回失败标志,告诉上层该线程没有获取到锁资源
    }    

3、tryAcquire方法是AQS的子类实现的,也就是ReentrantLock的两个静态内部类实现的,目的就是通过CAS尝试获取锁资源,
   获取锁资源成功则返回true,获取锁资源失败则返回false; 

hasQueuedPredecessors

非公平锁与公平锁的区别就是获取锁的时候,公平锁多了上述方法的判断

public final boolean hasQueuedPredecessors() {
        Node t = tail;  //尾节点
        Node h = head;  //头节点
        Node s;

        //头节点 != 尾节点
        //同步队列第一个节点不为null
        //当前线程是同步队列第一个节点
        return h != t &&
                ((s = h.next) == null || s.thread != Thread.currentThread());
    }

这个方法用于判断Node链表中,是否存在阻塞Node

这里分情况来看,分别为

  • head,tail都未初始化,那不用问,直接false
  • head,tail已初始化
    • 头尾都指向一个Node,也直接返回false,当只有一个Node的时候,只有一种情况,那就是,已经唤醒一个阻塞Node,并且该Node已经持有锁,这里可以尝试获取锁,说不定就可以获取到锁
    • Node链表数量大于等于2
      • s为head的下一个节点,s为null就是上一种情况,s.thread,就是等待尝试获取锁的线程,AQS中,CLH同步队列,第一个节点的线程要么为null,要么就是当前持有锁的线程,而第二个节点的线程可以不断自旋的尝试获取锁,因为有可能在当前持有锁的线程释放锁唤醒第二个节点之前获取到锁

addWaiter(Node)

1、源码:
    /**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    private Node addWaiter(Node mode) {
        // 按照给定的mode模式创建新的结点,模式有两种:Node.EXCLUSIVE独占模式、Node.SHARED共享模式;
        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) { // 如果pred不为空,说明该队列已经有结点了
            node.prev = pred;
            if (compareAndSetTail(pred, node)) { // 通过CAS尝试将node结点设置为队尾结点
                pred.next = node;
                return node;
            }
        }
        // 执行到此,说明队尾没有元素,则进入自旋首先设置头结点,然后将此新建结点添加到队尾
        enq(node); // 进入自旋添加node结点
        return node;
    }
    
2、    addWaiter通过传入不同的模式来创建新的结点尝试加入到队列尾部,如果由于并发导致添加结点到队尾失败的话那么就进入自旋将结点加入队尾;

enq(Node)

 1、源码:
    /**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) {
        for (;;) { // 自旋的死循环操作方式
            Node t = tail;
            // 因为是自旋方式,首次链表队列tail肯定为空,但是后续链表有数据后就不会为空了
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node())) // 队列为空时,则创建一个空对象结点作为头结点,无意思,可认为傀儡结点
                    tail = head; // 空队列的话,头尾都指向同一个对象
            } else {
                // 进入 else 方法里面,说明链表队列已经有结点了
                node.prev = t;
                // 因为存在并发操作,通过CAS尝试将新加入的node结点设置为队尾结点
                if (compareAndSetTail(t, node)) { 
                    // 如果node设置队尾结点成功,则将之前的旧的对象尾结点t的后继结点指向node,node的前驱结点也设置为t
                    t.next = node;
                    return t;
                }
            }
            
            // 如果执行到这里,说明上述两个CAS操作任何一个失败的话,该方法是不会放弃的,因为是自旋操作,再次循环继续入队
        }
    }

2、enq通过自旋这种死循环的操作方式,来确保结点正确的添加到队列尾部,通过CAS操作如果头部为空则添加傀儡空结点,然后在循环添加队尾结点;

compareAndSetHead/compareAndSetTail

1、源码:
    /**
     * CAS head field. Used only by enq.
     */
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }
    
    /**
     * CAS tail field. Used only by enq.
     */
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }    

2、CAS操作,设置头结点、尾结点;

acquireQueued(Node, int)

1、源码:
    /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    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
                    // 拿到锁资源后,则该node结点升级做头结点,且设置后继结点指针为空,便于GC回收
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) && // 根据前驱结点看看是否需要休息一会儿
                    parkAndCheckInterrupt()) // 阻塞操作,正常情况下,获取不到锁,代码就在该方法停止了,直到被唤醒
                    interrupted = true;
                    
                // 如果执行到这里,说明尝试休息失败了,因为是自旋操作,所以还会再次循环继续操作判断
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

2、acquireQueued也是采用一个自旋的死循环操作方式,只有头结点才能尝试获取锁资源,其余的结点挨个挨个在那里等待修改,等待被唤醒,等待机会成为头结点;
   而新添加的node结点也自然逃不过如此命运,先看看是否头结点,然后再看看是否能休息;

这个方法有很多人叫做,将当前节点加入到阻塞队列中,然后到parkAndCheckInterrupt中休眠,等待前驱节点用完锁再唤醒自己,唤醒后再把自己这节点设置为head,state设置为1。

shouldParkAfterFailedAcquire(Node, Node)

1、源码:
    /**
     * Checks and updates status for a node that failed to acquire.
     * Returns true if thread should block. This is the main signal
     * control in all acquire loops.  Requires that pred == node.prev.
     *
     * @param pred node's predecessor holding status
     * @param node the node
     * @return {@code true} if thread should block
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus; // 获取前驱结点的状态值
        if (ws == Node.SIGNAL) // 若前驱结点的状态为SIGNAL状态的话,那么该结点就不要想事了,直接返回true准备休息
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            // 若前驱结点的状态为CANCELLED状态的话,那么就一直向前遍历,直到找到一个不为CANCELLED状态的结点
            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状态,然后返回false标志等外层循环再次判断
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

2、shouldParkAfterFailedAcquire主要是检测前驱结点状态,前驱结点为SIGNAL的话,则新结点可以安安心心休息了;
   如果前驱结点大于零,说明前驱结点处于CANCELLED状态,那么则以入参pred前驱为起点,一直往前找,直到找到最近一个正常等待状态的结点;
   如果前驱结点小于零,那么就将前驱结点设置为SIGNAL状态,然后返回false依赖acquireQueued的自旋再次判断是否需要进行休息;

parkAndCheckInterrupt()

1、源码:
    /**
     * Convenience method to park and then check if interrupted
     *
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this); // 阻塞等待
        return Thread.interrupted(); // 被唤醒后查看是否有被中断过否?
    }

2、parkAndCheckInterrupt首先调用park让线程进入等待状态,然后当park阻塞被唤醒后,再次检测是否曾经被中断过;
   而被唤醒有两种情况,一个是利用unpark唤醒,一个是利用interrupt唤醒;

unlock()

1、源码:
    public void unlock() {
        sync.release(1); // 
    }

2、unlock释放锁资源,一般都是在finally中被调用,防止当临界区因为任何异常时怕锁不被释放;
   而释放锁不像获取锁lock的实现多色多样,没有所谓公平或不公平,就是规规矩矩的释放资源而已;

release(int)

1、源码:
    /**
     * Releases in exclusive mode.  Implemented by unblocking one or
     * more threads if {@link #tryRelease} returns true.
     * This method can be used to implement method {@link Lock#unlock}.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryRelease} but is otherwise uninterpreted and
     *        can represent anything you like.
     * @return the value returned from {@link #tryRelease}
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) { // 尝试释放锁资源,此方法由AQS的具体子类实现
            Node h = head;
            if (h != null && h.waitStatus != 0) // 从头结点开始,唤醒后继结点
                unparkSuccessor(h); // 踢出CANCELLED状态结点,然后唤醒后继结点
            return true;
        }
        return false;
    }

2、release尝试释放锁,并且有义务移除CANCELLED状态的结点,还有义务唤醒后继结点继续运行获取锁资源;

tryRelease(int)

1、源码:
    // NonfairSync 和 FairSync 的父类 Sync 类的 tryRelease 方法    
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases; // 获取锁资源值并做减1操作
        if (Thread.currentThread() != getExclusiveOwnerThread()) // 查看当前线程是否和持有锁的线程是不是同一个线程
            // 正常情况下,需要释放的线程肯定是持有锁的线程,否则不就乱套了,肯定哪里出问题了,所以抛出异常
            throw new IllegalMonitorStateException(); 
        boolean free = false;
        if (c == 0) { // 若此时锁资源值做减法操作后正好是0,则所有锁资源已经释放干净,因此持有锁的变量也置为空
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c); // 若此时做减法操作还没有归零,那么这种情况就是那种重入锁,需要重重释放后才行
        return free;
    }

2、tryRelease主要通过CAS操作对state锁资源进行减1操作;

unparkSuccessor(Node)

1、源码:
    /**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     */
    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.
         */
        // 该node一般都是传入head进来,也就是说,需要释放头结点,也就是当前结点需要释放锁操作,顺便唤醒后继结点
        int ws = node.waitStatus;
        if (ws < 0) // 若结点状态值小于0,则归零处理,通过CAS归零,允许失败,但是不管怎么着,仍然要往下走去唤醒后继结点
            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; // 取出后继结点,这个时候一般都是Head后面的一个结点,所以一般都是老二
        if (s == null || s.waitStatus > 0) { // 若后继结点为空或者后继结点已经处于CANCELLED状态的话
            s = null;
            // 那么从队尾向前遍历,直到找到一个小于等于0的结点
            // 这里为什么要从队尾向前寻找?
            // * 因为在这个队列中,任何一个结点都有可能被中断,只是有可能,并不代表绝对的,但有一点是确定的,
            // * 被中断的结点会将结点的状态设置为CANCELLED状态,标识这个结点在将来的某个时刻会被踢出;
            // * 踢出队列的规则很简单,就是该结点的前驱结点不会指向它,而是会指向它的后面的一个非CANCELLED状态的结点;
            // * 而这个将被踢出的结点,它的next指针将会指向它自己;
            // * 所以设想一下,如果我们从head往后找,一旦发现这么一个处于CANCELLED状态的结点,那么for循环岂不是就是死循环了;
            // * 但是所有的这些结点当中,它们的prev前驱结点还是没有被谁动过,所以从tail结点向前遍历最稳妥

            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread); // 唤醒线程
    }

2、unparkSuccessor主要是踢出CANCELLED状态结点,然后唤醒后继结点;
   但是这个唤醒的后继结点为空的话,那么则从队尾一直向前循环查找小于等于零状态的结点并调用unpark唤醒;

结构图:

共享锁流程图:

独占锁流程图:

参考:

Java并发包基石-AQS详解

并发编程面试必备:AQS 原理以及 AQS 同步组件总结

深入学习java同步器AQS

Java并发之AQS源码分析(一)

浅谈Java并发编程系列(九)—— AQS结构及原理分析

深入分析AQS实现原理

原理剖析(第 005 篇)AQS工作原理分析

AbstractQueuedSynchronizer 原理分析 - Condition 实现原理

AbstractQueuedSynchronizer(AQS源码解读)

AQS源码分析

JUC AQS ReentrantLock源码分析(一)

Java并发编程——AQS源码解析

AQS源码分析(AbstractQueuedSynchronizer)

【Java并发编程实战】----- AQS(二):获取锁、释放锁

AQS

AQS源码分析-共享模式

Java并发之AQS源码分析(一)

沉淀再出发:关于java中的AQS理解

深入剖析基于并发AQS的(独占锁)重入锁(ReetrantLock)及其Condition实现原理

硬核干货:5W字17张高清图理解同步器框架AbstractQueuedSynchronizer

posted @ 2019-10-18 14:51  hongdada  阅读(1117)  评论(0编辑  收藏  举报