并发编程系列(十)AQS同步器独占锁加锁与解锁-源码解读

 

线程池的线程特殊是AQS的子类,通过类图可以看到无论是公平锁还是非公平锁都是依赖AbstractQueuedSynchronizer实现的;

AbstractOwnableSynchronizer是AbstractQueuedSynchronizer的父类为创建可能需要所有权概念的锁和相关同步器提供了基础,首先我们膜拜下并发包大神Doug Lea的代码

1.AbstractOwnableSynchronizer源码分析

/**
 *可能由线程独占的同步器。这个
 *类为创建锁和相关同步器提供了基础
 *这可能需要一个所有权的概念。这个
 *{@code AbstractOwnableSynchronizer}类本身不管理或
 *使用此信息。但是,子类和工具可以使用
 *适当维护的值有助于控制和监视访问
 *并提供诊断。
 *
 * @since 1.6
 * @author Doug Lea
 */
public abstract class AbstractOwnableSynchronizer
        implements java.io.Serializable {
    /** Use serial ID even though all fields transient. */
    private static final long serialVersionUID = 3737899427754241961L;
    /**
     * 子类使用的空构造函数
     */
    protected AbstractOwnableSynchronizer() { }
    /**
     * 独占模式同步的当前所有者。
     */
    private transient Thread exclusiveOwnerThread;
    /**
     *设置当前拥有独占访问权限的线程。
     *{@code null}参数表示没有线程拥有访问权限。
     *此方法不强制任何同步或
     *{@code volatile}字段访问。
     *@param thread所有者线程
     */
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
    /**
     * Returns the thread last set by {@code setExclusiveOwnerThread},
     * or {@code null} if never set.  This method does not otherwise
     * impose any synchronization or {@code volatile} field accesses.
     * @return the owner thread
     *返回由{@code setexclusioneownerthread}最后设置的线程,
     *或者{@code null}如果从未设置。否则,这种方法不会
     *强制任何同步或{@code volatile}字段访问。
     *@返回所有者线程
     */
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

2.AQS同步器下的Node源码:

//AbstractQueuedSynchronizer的内部类
/**
 * 等待队列的节点类
 * 通过Node可以实现两个队列
 * 1通过prev和next实现CLH队列(线程同步队列,双向队列)
 * 2nextWaiter实现Condition条件上的等待队列(单项队列)
 */
static final class Node {
    /** 标示节点当前在共享模式下 */
    static final Node SHARED = new Node();
    /** 标示当前节点在独占模式下 */
    static final Node EXCLUSIVE = null;
    /** 下面的几个int值常量是给waitStatus使用 */
    /** 线程已取消*/
    static final int CANCELLED =  1;
    /** 其表示当前node的后继节点需要被唤醒 */
    static final int SIGNAL    = -1;
    /** 线程等待codition条件*/
    static final int CONDITION = -2;
    /**
     * 共享模式Node有可能处在这种状态,表示锁的下一个获取可以无条件传播
     */
    static final int PROPAGATE = -3;
    /**
     *取值范围只可能是:
     *      SIGNAL:表示当前node的后继节点对应的线程需要被唤醒
     *      CANCELLED:此线程已经取消,可能是超时或者中断
     *      CONDITION:次node当前处于提哦啊见队列中
     *      PEOPAGATE:当前场景下后续的acquierShared能够得以执行,
     *      0     :对于正常的同步节点,该字段初始化为0
     */
    volatile int waitStatus;
    /**
     *当前结点的前驱结点,用于检查waitStatus
     * 如当前结点取消,那就需要前驱结点和后继节点来完成连接
     */
    volatile Node prev;
    /**
     * 指向当前结点在释放时唤醒的后继节点
     */
    volatile Node next;
    /**
     * 入队时的当前线程
     */
    volatile Thread thread;

    /**
     * 存储condition队列中的后继节点
     */
    Node nextWaiter;
    /**
     * 如果是在共享模式下等待,返回true
     */
    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) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }
    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

3.AbstractQueuedSynchronizer 独占锁

独占锁至少有两个功能:1获取锁的功能当多个线程一起获取锁的时候,只有一个线程能够获取到锁,其他线程必须在当前位置阻塞等待

2释放锁的功能,获取锁的线程释放锁资源,而且必须唤醒正在等待锁资源的一个线程

4.1.1AQS独占锁加锁的过程

4.1.2 acquire()方法:

	/**
	 以独占模式获取,忽略中断。
	 *通过至少调用一次{@link#tryAcquire},
	 *回归成功。否则线程将排队,可能
	 *反复阻塞和解除阻塞,调用{@link
	 *努力获得成功。这种方法可以使用
	 *实现方法{@link Lock#Lock}。
	 *
	 * @param arg获取的参数。此值传递给
	 */
	public final void acquire(int arg) {
		if (!tryAcquire(arg) &&
				acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
			selfInterrupt();
	}

代码示意:

(1)调用tryAcquire() 方法,返回true则结束(tryAcquire一般都是在AQS的子类中实现)

(2)如果调用tryAcquire返回的fasle则调用addWaiter() 方法入队

(3)调用acquireQueued方法等待队列中获取资源;

步骤如下:

  1. tryAcquire方法尝试以独占方式获取资源
  2. 在AQS中是一个抛出异常的方法,想要调用子类必须重写该方法;
  3. 一般state发表资源,不同的锁意义不尽相同,获取到资源后,返回true
  4. 返回fasle,进入下一个流程
  5. addWaiter()方法;将线程加入队列尾部,并且标记为独占模式
  6. acquireQueued()方法,让线程在等待中获取资源才返回
  7. 如果等待过程中被中断则返回true,否则返回fasle
  8. 在 6 中如果被中断过他是不响应的,只有在获取资源后才能进行自我中断,设置中断标识

4.1.3 tryAcquire()方法

tryAcquire()在AQS中是空方法,子类要重写该方法才能调用,这里没有定义abstract方法是为了独占锁重写tryAcquire和tryRelease,共享锁重写tryAcquireShared和tryReleaseShared如果定义为abstract所有子类都要重写

4.1.4 addWaiter()方法

tryAcquire是子类要实现的方法;这里不在过多的介绍

    /**
     * 加入独占线程节点到队尾上
     */
    private Node addWaiter(Node mode) {
        //用当前线程创建一个Node结点
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //尝试快读加入队列成功则返回新的结点Node
        //失败,则采用自旋加入结点直至成功返回该节点
        Node pred = tail;
        //队尾非空
        if (pred != null) {
            node.prev = pred;
            //如果CAS入队尾成功 返回结点
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //如果队尾为空
        //或者通过CAS进入队尾失败,存在竞争
        //通过end()方法自旋
        enq(node);
        return node;
    }

4.1.5  enq()方法 自旋方式使node进入队尾

    /**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     *自旋方式使node进入队尾
     */
    private Node enq(final Node node) {
        //自旋
        for (;;) {
            //队尾结点
            Node t = tail;
            //如果队列为空
            if (t == null) { // Must initialize
                //创建head结点
                //CAS设置队列头结点
                if (compareAndSetHead(new Node()))
                    //此时队列只有一个结点
                    //头结点等于尾结点
                    tail = head;
            } else {
                //设置node结点的prev引用指向t
                //node.prev = t;
                 //CAS设置新的队尾结点为node
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    //原队尾结点的next引用指向node
                    //node就是新的节点tail
                    t.next = node;
                    //自旋结束,返回原尾结点
                    return t;
                }
            }
        }
    }

4.1.6 acquireQueued() 方法:节点加入队列后,尝试在等待队列中自旋获取资源

    /**
     *结点加入队列后,尝试在等待队列中自旋获取资源
     */
    final boolean acquireQueued(final Node node, int arg) {
        //标记是否拿到资源
        boolean failed = true;
        try {
            //标记是否中断
            boolean interrupted = false;
            //自旋
            for (;;) {
                //node的前驱结点,会抛出NullPointerException
                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 {
            if (failed)
                cancelAcquire(node);
        }
    }

4.1.7 shouldParkAfterFailedAcquire() 线程获取资源失败后,判断是否阻塞线程


    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //获得前驱结点的状态
         //根据前驱结点的状态判断是否将线程阻塞
        int ws = pred.waitStatus;
        //如果前驱结点已经被设置为SIGNAL状态
        if (ws == Node.SIGNAL)
            /**
            *前驱结点释放资源后马上唤醒后继节点
            *返回true 表示阻塞线程
            */
            return true;
        //如果前驱结点的线程被撤销,跳过所有的被撤销的pro结点
        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.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

4.1.8 parkAndCheckinterrupt()方法

/**
 *阻塞线程,判断阻塞线程是否中断
 */   
private final boolean parkAndCheckInterrupt() {
        //park()会让当前线程进入waiting状态
        //在此状态下,有两种途径可以唤醒该线程
        //1:unpark()
        //2:interrupt()
        LockSupport.park(this);
        //返回线程是否被中断,会清除中断标识
        return Thread.interrupted();
    }

4.1.9 selfInterrupt()方法

中断线程,并且不对中断做出响应

   static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

总结:accquire方法流程

1:尝试获取资源,如果获取资源成功,tryAcquire()方法返回true,则accquire方法执行结束,否则进入步骤2

2:如果尝试获取资源失败,tryAccquire方法返回false,则执行addWaiter()方法将线程以结点的方式加到队列的末尾,

3:addWaiter方法也许会存在竞争(并发执行的)造成入队为失败,需要通过自旋方式;

4:入队尾成功后,通过accquireQueued方法尝试再队列中获取资源或者阻塞线程,

5:park方法阻塞线程后,等待前驱结点调用unpark方法或者线程中断来唤醒线程

6:判断线程是否中断并且维护线程中断的标识

5 独占锁解锁过程

5.1 release()方法

/**
*释放独占的资源
*会唤醒等待队列中的其他线程来获取资源
*/    
public final boolean release(int arg) {
        //如果tryRelease释放资源成功
        if (tryRelease(arg)) {
            //队列中的头结点
            Node h = head;
            //头结点非空,并且头结点的waitStatus不等于0
            if (h != null && h.waitStatus != 0)
                //唤醒后继节点,让后继节点竞争资源
                unparkSuccessor(h);
            //释放独占的资源成功,放回true
            return true;
        }
        //释放独占资源失败,返回false
        return false;
    }

5.2唤醒后继节点

    /**
     * 唤醒后继节点
     */
    private void unparkSuccessor(Node node) {
        /*
         *节点的状态
           如果小于0,则肯定不是CANCELLED状态
         */
        int ws = node.waitStatus;
        //cas将节点状态修改为0
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * 节点node的后继节点
         */
        Node s = node.next;
        //如果后继节点非空,且状态大于0,即CANCELLED
        //说明后继节点的线程取消对资源的等待
        if (s == null || s.waitStatus > 0) {
            //将后继节点至为空
            s = null;
            //从队尾结点开始向前遍历
            //找到队列中的node节点后第一个等待唤醒的节点
            //如果遍历到节点t非空且不等于当前结点node
            //则校验节点t的状态
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

unparkSuccessor()方法执行流程

1:将node节点的状态设置为0

2:寻找到下一个非取消状态的节点s

3:如果节点s部位null,则调用LockSupport.unpark(s.thread)方法唤醒s所在的线程(唤醒线程也是有顺序的,就是添加到CLH队列的顺序)

鸟欲高飞先振翅,人求上进先读书-

                                                                      ------------------李苦禅-----------------

发布了55 篇原创文章 · 获赞 3 · 访问量 5251

猜你喜欢

转载自blog.csdn.net/qq_38130094/article/details/103540315