什么是重入锁和AQS

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是重入锁和AQS相关的知识,希望对你有一定的参考价值。

参考技术A 什么是重入锁

java.util.concurrent.locks.ReentrantLock

ReenTrantLock独有的能力:

1.      ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。

2.      ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

3.      ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。

非重入锁

当A方法获取锁去锁住一段需要做原子性操作的B方法时,如果这段B方法又需要锁去做原子性操作,那么A方法就必定要与B方法出现死锁。这种会出现问题的重入一把锁的情况,叫不可重入锁。

lock的过程:

   首先尝试获取资源,如果当前状态为0,表示没有线程占有锁,设置该线程为独占模式,使用CAS设置状态,否则如果当前线程和独占线程是一个线程,修改状态值,否则返回false。

  若获取资源失败,则通过addWaiter <-(aqs)方法创建一个节点并放在CLH队列的尾部。head tail未初始化会创建虚拟节点同时指向

为什么 AQS 需要一个虚拟 head 节点

每个节点都需要设置前置Node 的 waitStatus  状态(这个状态为是为了保证数据一致性),防止重复释放操作。而第一个节点是没有前置节点的,所以需要创建一个虚拟节点。

  逐步去执行CLH队列中的线程,当前线程会公平性的阻塞一直到获取锁为止,返回线程在等待的过程中还是否中断过。

unlock的过程

一次unlock操作需要修改状态位,然后唤醒节点。整个释放操作也是使用unpark()来唤醒队列最前面的节点。其实lock中比较重要的也就是lock和release,它们又和AQS联系紧密,下面会单独谈谈AQS的重要方法。

Condition的await和signal

wait和notify/notify VS await和signal

Condition能够支持不响应中断,而通过使用Object方式不支持;

Condition能够支持多个等待队列(new 多个Condition对象),而Object方式只能支持一个;

Condition能够支持超时时间的设置,而Object不支持

对标Object的wait方法

void await() throws InterruptedException:当前线程进入等待状态,如果其他线程调用condition的signal或者signalAll方法并且当前线程获取Lock从await方法返回,如果在等待状态中被中断会抛出被中断异常;

long awaitNanos(long nanosTimeout):当前线程进入等待状态直到被通知,中断或者超时;

boolean await(long time, TimeUnit unit)throws InterruptedException:同第二种,支持自定义时间单位

boolean awaitUntil(Date deadline) throws InterruptedException:当前线程进入等待状态直到被通知,中断或者到了某个时间

对标Object的notify/notifyAll方法

void signal():唤醒一个等待在condition上的线程,将该线程从等待队列中转移到同步队列中,如果在同步队列中能够竞争到Lock则可以从等待方法中返回。

void signalAll():与1的区别在于能够唤醒所有等待在condition上的线程

如图所示,ConditionObject是AQS的内部类,因此每个ConditionObject能够访问到AQS提供的方法,相当于每个Condition都拥有所属同步器的引用。

调用condition.await方法的线程必须是已经获得了lock,也就是当前线程是同步队列中的头结点。调用该方法后会使得当前线程所封装的Node尾插入到等待队列中。

如图,线程awaitThread先通过lock.lock()方法获取锁成功后调用了condition.await方法进入等待队列,而另一个线程signalThread通过lock.lock()方法获取锁成功后调用了condition.signal或者signalAll方法,使得线程awaitThread能够有机会移入到同步队列中,当其他线程释放lock后使得线程awaitThread能够有机会获取lock,从而使得线程awaitThread能够从await方法中退出执行后续操作。如果awaitThread获取lock失败会直接进入到同步队列。

// 线程已被取消

    static final int CANCELLED =  1;

    // 当前线程的后继线程需要被unpark(唤醒)

    // 一般发生情况是:当前线程的后继线程处于阻塞状态,而当前线程被release或cancel掉,因此需要唤醒当前线程的后继线程。

    static final int SIGNAL    = -1;

    // 在Condition休眠状态,在等待Condition唤醒

    static final int CONDITION = -2;

    // (共享锁)其它线程获取到“共享锁”,对应的waitStatus的值

    static final int PROPAGATE = -3;

volatile int waitStatus;

---------------------

/**

    * 这个方法也就是lock()方法的关键方法。tryAcquire获得资源,返回true,直接结束。若未获取资源,新建一个节点插入队尾,

*addWaiter用于添加节点,也就是把当前线程对应的节点插入CLH队列的尾部。

    * @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();

   

---------------------

protected final boolean tryAcquire(int acquires)

            final Thread current = Thread.currentThread();

            int c = getState();

            if (c == 0)

                if (!hasQueuedPredecessors() &&

                    compareAndSetState(0, acquires))

                    setExclusiveOwnerThread(current);

                    return true;

               

           

            else if (current == getExclusiveOwnerThread()) //判断是否持有锁的是自己,重入

                int nextc = c + acquires;

                if (nextc < 0)

                    throw new Error("Maximum lock count exceeded");

                setState(nextc);

                return true;

           

            return false;

       

---------------------

  * 非公平锁

    */

    static final class NonfairSync extends Sync

        private static final long serialVersionUID = 7316153563782823691L;

        /**

        * Performs lock.  Try immediate barge, backing up to normal

        * acquire on failure.

        */

        final void lock()

            if (compareAndSetState(0, 1))//CAS设置当前为0 的时候上锁

                setExclusiveOwnerThread(Thread.currentThread());

            else

                acquire(1);//否则尝试获得锁。

       

        protected final boolean tryAcquire(int acquires)

            return nonfairTryAcquire(acquires);

       

   

    /**

    * 公平锁

    */

    static final class FairSync extends Sync

        private static final long serialVersionUID = -3000897897090466540L;

        final void lock()

            acquire(1);

       

        /**

        *

        */

        protected final boolean tryAcquire(int acquires)

            final Thread current = Thread.currentThread();

            int c = getState();

            if (c == 0)

                if (!hasQueuedPredecessors() &&

                    compareAndSetState(0, acquires)) //没有前驱节点并且CAS设置成功

                    setExclusiveOwnerThread(current);//设置当前线程为独占线程

                    return true;

               

           

            else if (current == getExclusiveOwnerThread()) //这里和非公平锁类似

                int nextc = c + acquires;

                if (nextc < 0)

                    throw new Error("Maximum lock count exceeded");

                setState(nextc);

                return true;

           

            return false;

       

   

以上是关于什么是重入锁和AQS的主要内容,如果未能解决你的问题,请参考以下文章

9AQS实现重入锁

显示锁和aqs

JUC并发编程 JUC AQS原理 -- AQS概述 & 实现不可重入锁

重入锁

可重入锁和不可重入锁

可重入锁和不可重入锁