java的AbstractQueuedSynchronizer实现原理

抛出问题:
1、ReentrantLock是如何实现线程等待的?
2、ReentrantLock是如何实现重入锁的?
3、ReentrantLock是如何实现公平锁和非公平锁?

J.U.C的ReentranLock底层是通过AQS同步器实现的,如图先看一下调用链
在这里插入图片描述
这里是非公平锁的调用链

ReentrantLock类有一个抽象静态内部类为Sync

/**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {

其中他有两个实现类NonfairSync,FairSync为非公平锁和公平锁

 /**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
/**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

下面分析ReentrantLock的非公平锁

 /**
     * Acquires the lock.
     *
     * <p>Acquires the lock if it is not held by another thread and returns
     * immediately, setting the lock hold count to one.
     *
     * <p>If the current thread already holds the lock then the hold
     * count is incremented by one and the method returns immediately.
     *
     * <p>If the lock is held by another thread then the
     * current thread becomes disabled for thread scheduling
     * purposes and lies dormant until the lock has been acquired,
     * at which time the lock hold count is set to one.
     */
    public void lock() {
        sync.lock();
    }
    此方法调用了NonfairSync的lock()方法


/**
 * Performs lock.  Try immediate barge, backing up to normal
 * acquire on failure.
 */
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
 NonfairSync的lock()方法先通过CAS判断AQS的state的属性是否为0,如果为0,
 说明没有线程占用锁,将state设置为1将当前线程设置成为独占线程。
 这是公平锁和非公平锁的区别,此时AQS的等待队列里可以有节点在等待,
 对于那些等待的节点来说不公平的。
 否则调用AQS的acquire(1);
  /**
 * Acquires in exclusive mode, ignoring interrupts.  Implemented
 * by invoking at least once {@link #tryAcquire},
 * returning on success.  Otherwise the thread is queued, possibly
 * repeatedly blocking and unblocking, invoking {@link
 * #tryAcquire} until success.  This method can be used
 * to implement method {@link Lock#lock}.
 *
 * @param arg the acquire argument.  This value is conveyed to
 *        {@link #tryAcquire} but is otherwise uninterpreted and
 *        can represent anything you like.
 */
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
此方法调用NonfairSync的tryAcquire的(arg)
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
然后代用Sync的nonfairTryAcquire(acquires);

     /**
             * Performs non-fair tryLock.  tryAcquire is implemented in
             * subclasses, but both need nonfair try for trylock method.
             */
            final boolean nonfairTryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {
                    if (compareAndSetState(0, acquires)) {
                        setExclusiveOwnerThread(current);
                        return true;
                    }
                }
                else if (current == getExclusiveOwnerThread()) {
                    int nextc = c + acquires;
                    if (nextc < 0) // overflow
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }
首先获取AQS的state属性,如果为0,则尝试使用CAS更改值为1,如果成功就获得锁
如果不成功,判断当前线程是不是独占锁的线程,如果是,则重入,将state值加1
如何返回false,则执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg);

    /**
         * 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) {
            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) {
                node.prev = pred;
                if (compareAndSetTail(pred, node)) {
                    pred.next = node;
                    return node;
                }
            }
            enq(node);
            return node;
        }
此方法是将当前线程封装成为AQS队列里的一个节点,首先是正常入队,如果队列尾不为空的话,将当前节点插入到队列尾,并且返回当前节点。否则执行enq(node);
/**
     * 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;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
    循环操作,创建一个没有没有数据的Node节点为头节点,将当前节点插入到队列的尾部,循环操作指导插入成功为止
    执行 /**
     * 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
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    判断当前节点的前驱节点是不是头结点,如果是则尝试后去锁,否则将节点线程挂起,使用LockSupport.park()方法挂起线程,这里具体实现细节就不说了,自己看源码。
1、ReentrantLock是如何实现线程等待的?
通过AQS同步器,里边有一个双向链表的队列实现的
2、ReentrantLock是如何实现重入锁的?
通过AQS的属性state,为0时没有线程获取锁,大于0时,有线程获取锁,等于几当前线程就重入多少次,重入多少次就得释放多少次
3、ReentrantLock是如何实现公平锁和非公平锁?*
通过源码中是否可以直接通过CAS将state的值从0变为1,对于队列里的线程是不公平的

总结:

1)首先基于CAS将state(锁数量)从0设置为1,如果设置成功,设置当前线程为独占锁的线程;–>请求成功–>第一次插队

2)如果设置失败(即当前的锁数量可能已经为1了,即在尝试的过程中,已经被其他线程先一步占有了锁),这个时候当前线程执行acquire(1)方法
* 2.1)acquire(1)方法首先调用下边的tryAcquire(1)方法,在该方法中,首先获取锁数量状态,
* 2.1.1)如果为0(证明该独占锁已被释放,当下没有线程在使用),这个时候我们继续使用CAS将state(锁数量)从0设置为1,如果设置成功,当前线程独占锁;–>请求成功–>第二次插队;当然,如果设置不成功,直接返回false
* 2.2.2)如果不为0,就去判断当前的线程是不是就是当下独占锁的线程,如果是,就将当前的锁数量状态值+1(这也就是可重入锁的名称的来源)–>请求成功
*
* 下边的流程一句话:请求失败后,将当前线程链入队尾并挂起,之后等待被唤醒。
*
* 2.2.3)如果最后在tryAcquire(1)方法中上述的执行都没成功,即请求没有成功,则返回false,继续执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法
* 2.2)在上述方法中,首先会使用addWaiter(Node.EXCLUSIVE)将当前线程封装进Node节点node,然后将该节点加入等待队列(先快速入队,如果快速入队不成功,其使用正常入队方法无限循环一直到Node节点入队为止)
* 2.2.1)快速入队:如果同步等待队列存在尾节点,将使用CAS尝试将尾节点设置为node,并将之前的尾节点插入到node之前
* 2.2.2)正常入队:如果同步等待队列不存在尾节点或者上述CAS尝试不成功的话,就执行正常入队(该方法是一个无限循环的过程,即直到入队为止)–>第一次阻塞
* 2.2.2.1)如果尾节点为空(初始化同步等待队列),创建一个dummy节点,并将该节点通过CAS尝试设置到头节点上去,设置成功的话,将尾节点也指向该dummy节点(即头节点和尾节点都指向该dummy节点)
* 2.2.2.1)如果尾节点不为空,执行与快速入队相同的逻辑,即使用CAS尝试将尾节点设置为node,并将之前的尾节点插入到node之前
* 最后,如果顺利入队的话,就返回入队的节点node,如果不顺利的话,无限循环去执行2.2)下边的流程,直到入队为止
* 2.3)node节点入队之后,就去执行acquireQueued(final Node node, int arg)(这又是一个无限循环的过程,这里需要注意的是,无限循环等于阻塞,多个线程可以同时无限循环–每个线程都可以执行自己的循环,这样才能使在后边排队的节点不断前进)
* 2.3.1)获取node的前驱节点p,如果p是头节点,就继续使用tryAcquire(1)方法去尝试请求成功,–>第三次插队(当然,这次插队不一定不会使其获得执行权,请看下边一条),
* 2.3.1.1)如果第一次请求就成功,不用中断自己的线程,如果是之后的循环中将线程挂起之后又请求成功了,使用selfInterrupt()中断自己
* (注意p==head&&tryAcquire(1)成功是唯一跳出循环的方法,在这之前会一直阻塞在这里,直到其他线程在执行的过程中,不断的将p的前边的节点减少,直到p成为了head且node请求成功了–即node被唤醒了,才退出循环)
* 2.3.1.2)如果p不是头节点,或者tryAcquire(1)请求不成功,就去执行shouldParkAfterFailedAcquire(Node pred, Node node)来检测当前节点是不是可以安全的被挂起,
* 2.3.1.2.1)如果node的前驱节点pred的等待状态是SIGNAL(即可以唤醒下一个节点的线程),则node节点的线程可以安全挂起,执行2.3.1.3)
* 2.3.1.2.2)如果node的前驱节点pred的等待状态是CANCELLED,则pred的线程被取消了,我们会将pred之前的连续几个被取消的前驱节点从队列中剔除,返回false(即不能挂起),之后继续执行2.3)中上述的代码
* 2.3.1.2.3)如果node的前驱节点pred的等待状态是除了上述两种的其他状态,则使用CAS尝试将前驱节点的等待状态设为SIGNAL,并返回false(因为CAS可能会失败,这里不管失败与否,都返回false,下一次执行该方法的之后,pred的等待状态就是SIGNAL了),之后继续执行2.3)中上述的代码
* 2.3.1.3)如果可以安全挂起,就执行parkAndCheckInterrupt()挂起当前线程,之后,继续执行2.3)中之前的代码
* 最后,直到该节点的前驱节点p之前的所有节点都执行完毕为止,我们的p成为了头节点,并且tryAcquire(1)请求成功,跳出循环,去执行。
* (在p变为头节点之前的整个过程中,我们发现这个过程是不会被中断的)
* 2.3.2)当然在2.3.1)中产生了异常,我们就会执行cancelAcquire(Node node)取消node的获取锁的意图。

后续会有condition源码分析和线程池源码分析,及并发编程的三个组件,敬请期待

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值