ReentrantLock锁

一、ReentrantLock 介绍

1、什么是 ReentrantLock?

       ReentrantLock是juc包下提供的一种锁,他是基于AQS来实现的;

       ReentrantLock既可以创建公平锁,也可以创建非公平锁,在ReentrantLock 内部分别定义了

       公平锁和非公平锁的内部类:FairSync(公平锁)、NonfairSync(非公平锁)

2、ReentrantLock 的构造方法

      ReentrantLock 中没有值得关注的属性,但有2个构造方法需要我们关注下,如下所示:

          

          

       由上边2个构造函数可以发现ReentrantLock 默认实现的是非公平锁(非公平锁性能比较高)

       若要创建公平锁 ReetrantLock,可以使用他的有参构造函数,并传入参数true

       ,如:new ReetrantLock(true)  创建的是公平锁的ReetrantLock。

二、ReentrantLock 与 Synchronized 之间的区别

       1、ReentrantLock是一个类,而 Synchronized 是一个关键字,他们都是在JVM层面实现

            互斥锁的方式

       2、如果线程竞争比较激烈的场景中,推荐使用 ReentrantLock,因为 Synchronized 有个

            锁升级的过程,若Synchronized处于 “轻量级锁 或 偏向锁”状态时,性能还是可以的,若

            Synchronized 处于 “重量级” 锁时,性能就比较低了,而且Synchronized 不存在锁降级

            ,一旦升级到重量级锁,之后 Synchronized  一直是重量级锁。

       3、实现原理不同,ReentrantLock基于AQS实现的,Synchronized 基于ObjectMonitor实现的

       4、ReentrantLock 功能比 Synchronized 更全面,如:

                  1)ReentrantLock 可以实现公平锁,也可以实现非公平锁;而 Synchronized只能实现

                        非公平锁。

                  2)ReentrantLock可以指定锁支援的等待时间              

       5、使用方式不同,ReentrantLock加锁之后需要手动释放锁,而Synchronized 可以自动

             释放锁。

三、加锁流程

      1、ReentrantLock 非公平锁加锁流程

            AQS内部类Node 的属性 waitStatus 用来表示 锁线程 的状态,waitStatus初始值为0;

            哪个线程通过CAS将 waitStatus 从0改为1,即表示竞争到了锁(即加锁成功)。

            若 waitStatus=-1,表示当前节点的下一个节点的线程是挂起的

                   

      公平锁的加锁流程与非公平锁的差不多,公平锁不需要进来就CAS竞争锁,而是直接进入AQS

      阻塞队列去排队。

 四、加锁源码解析

 1、lock方法

                    ReetrantLock 类中,lock() 是一个接口,它的是由2个子类 FairSync 和 NonFairSync

                    实现的;    

/**
  非公平锁的加锁方法
*/
final void lock() {
            //非公平锁直接抢锁,不管有没有线程在排队
            //同步状态为0:表示没有线程持有锁,
            // 若锁状态能从0修改成1表示当前线程抢到了锁,并设置当前线程持有锁
            if (compareAndSetState(0, 1))
                //设置当前线程持有锁
                setExclusiveOwnerThread(Thread.currentThread());
            else//若当前线程已经持有锁,则同步状态加1,即锁的个数加1(这个过程称为锁重入)
                //锁重入,这个方法最终执行的是下面的方法 tryAcquire
                acquire(1);//枪锁失败,进入AQS标准枪锁流程(即当前线程放入竞争锁队列)
        }


/**
         * 加锁,lock.lock() 方法
         * 每次加锁参数都是1
            公平锁的加锁方法
         */
        final void lock() {
            //没有尝试抢锁,直接进入AQS标准抢锁流程
            //调用AQS中的方法(请参考前边的AQS笔记)
            acquire(1);
        }

2、tryAcquire 方法

         tryAcquire方法的功能是尝试获取锁资源,若获取成功返回true,获取失败返回false;

         tryAcquire 定义在 AQS中,但是由各个子类来实现的。在ReetrantLock中 tryAcquire 是

         分别在内部类 FairSync(公平锁)和 NonfairSync(非公平锁)实现的。

        1)tryAcquire 在内部类 FairSync(公平锁)中的实现

              

/**
         * 公平锁的方式加锁
         * AQS调用,子类自己实现获取锁的流程
         */
        
        protected final boolean tryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取当前同步(锁)状态
            int c = getState();
            //c==0表示当前其他持有锁的线程释放了锁,当前情况是锁没有被线程持有
            if (c == 0) {
                /*
                 * hasQueuedPredecessors方法是看下队列中有没有其他线程在排队,即锁没有被持有的情况下,是否存在有其他线程在排队,
                 * 若不存在比当前线程等待时间更长的线程(即当前线程在AQS阻塞队列头部),且更改同步状态成功,则设置当前线程持有锁,并返回true;
                 * 否则获取锁失败,返回false
                 *
                 * todo 公平锁与非公平锁的区别?
                 *   公平锁下,当当前线程去竞争锁时,若锁没有被其他线程持有,则先判断同步器(AbstractQueuedSynchronizer)中
                 *   的阻塞队列中有没有比当前线程 “等待时间更长的线程”,若有则不回获取锁,把锁让给等待时间最长的线程,所以锁的
                 *   获取是公平的;
                 *   非公平锁下,只要锁没有被其他线程持有,则当前线程立即获取锁,不管它是不是等待时间最长的线程
                 *
                 */
                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;
        }
    }


/**  
         * 判断 是否存在比当前线程等待时间长的线程或节点(节点关联线程)
         * AQS的方法
         */
public final boolean hasQueuedPredecessors() {
             
        Node t = tail; // Read fields in reverse initialization order  按反向初始化顺序读取节点
        Node h = head;
        Node s;
        //若队列不为空,且队列中除了头节点外第一个节点中关联的线程不是当前线程,
        //则返回true
        //todo 在并发条件下,在判断 h != t 成立后,后面 s = h.next 可能为空,这种情况下也认为存在有比当前线程等待时间长的线程
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());//todo 注意:在队列不为空的情况下,s一定不为空,
    }

        2)tryAcquire 在内部类 NonfairSync(非公平锁)中的实现

             tryAcquire在 NonfairSync的实现很简单,主要调用是内部类Sync的

             方法 nonfairTryAcquire

 //查看当前线程是否能获取锁
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);//非公平锁标准获取流程
        }

         3)nonfairTryAcquire(int acquires) 方法

               nonfairTryAcquire 是ReetrantLock内部类Sync的方法,主要是用来实现非公平锁获取

               锁的过程

               nonfairTryAcquire 方法代码如下:

final boolean nonfairTryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取当前的同步状态(即锁是否被持有)
            int c = getState();
            //执行到这里时,正好其他持有锁的线程释放了锁,可以尝试枪锁,
            //c==0,表示锁没有被持有
            if (c == 0) {//锁没有线程持有
                //继续枪锁,不看有没有线程排队
                if (compareAndSetState(0, acquires)) {//枪锁,并修改同步状态,即表示设置锁被线程持有
                    //枪锁成功,则设置锁被当前线程持有
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//若当前线程就是持有锁的线程,则锁重入
                //设置新的同步状态,即锁重入,
                //同步状态即是加锁的次数
                //使用 state 记录锁重入次数
                int nextc = c + acquires;
                // 超过了int类型范围,表明符号溢出,所以抛出异常
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //将重入次数赋值给变量state
                setState(nextc);
                return true;
            }
            //获取锁失败,表明当前线程通过AQS将当前线程放入阻塞队列,然后进行阻塞操作等待线程被唤醒获取锁
            return false;
        }

3、tryLock 方法

         我们需要分析无参数的 tryLock() 和 带超时时间的 tryLock(long timeout, TimeUnit unit)

          1)tryLock() 方法

               该很简单,他调用 ReetrantLock内部类Sync的nonfairTryAcquire 方法 

                去获取锁,若获取锁成功则返回true,获取锁失败返回false

                tryLock() 代码如下:

                      

               方法 nonfairTryAcquire 的解析在上边。

           2)tryLock(long timeout, TimeUnit unit) 

                 该方法功能也是尝试获取锁,与tryLock()的区别是:若等待时间超过 timeout,则直接

                 返回fase,表示获取锁失败。

                 tryLock(long timeout, TimeUnit unit) 结构如下:

public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }



/**
     * 独占式的同步状态获取(即锁的获取)(互斥锁),可响应中断,并具有超时控制;
     * 线程在队列中 阻塞和非阻塞 交替变换的结束条件为:同步状态获取成功、被中断、超时
     * 这三种情况
     *
     */

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())//被中断,则抛出异常
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout); //
    }


/**
     * Acquires in exclusive timed mode.
     *
     * (当前线程)采用独占计时模式获取锁
     *
     * @param arg the acquire argument   acquire 方法参数
     * @param nanosTimeout max wait time  最大等待时间,单位纳秒
     * @return {@code true} if acquired
     */
    private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        //若等待时间小于等于0,同步状态获取失败
        if (nanosTimeout <= 0L)
            return false;
        //计算同步状态的结束时间点 System.nanoTime():返回当前时间的纳秒数
        /*
         * 针对超时控制,程序首先计算超时的截止时间点deadline,deadline = System.nanoTime() + nanosTimeout。
         * 如果获取同步状态失败,则需要重新计算休眠的时间间隔nanosTimeout = deadline - System.nanoTime(),
         * 如果nanosTimeout <= 0 表示已经超时了,返回false,如果nanosTimeout > spinForTimeoutThreshold(1000纳秒),
         * 使当前线程等待nanosTimeout纳秒,否则,如果nanosTimeout <= spinForTimeoutThreshold,那该线程将不会超时等待,
         * 而是进入快速的自旋过程。原因在于:非常短的超时等待无法做到十分精确,如果这时再进行超时等待,相反会让nanosTimeout的
         * 超时从整体上表现得反而不精确。因此,在超时非常短的情况下,同步器会进入无条件的快速自旋。
         */
        final long deadline = System.nanoTime() + nanosTimeout;
        //采用独占模式 Node.EXCLUSIVE 为当前线程创建一个节点,并把节点添加到队列中,返回新创建的节点
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            //死循环,是一个“自旋”过程,自旋获取同步状态
            for (;;) {
                final Node p = node.predecessor();
                //只有node 的前置节点为头节点,当前线程才能够获取锁成功
                //若当前线程成功了获取锁,则退出自循环
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                //重新计算超时时间间隔
                nanosTimeout = deadline - System.nanoTime();
                //若超时时间小于等于0,则获取同步状态失败
                if (nanosTimeout <= 0L)
                    return false;
                //若节点node获取节点失败且 剩余时间 nanosTimeout > spinForTimeoutThreshold),
                // 则当前线程阻塞,继续等待,
                //若当前线程获取锁失败要接入阻塞状态
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())//若当前线程在阻塞状态下被中断,则抛出异常
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

          注意:tryAcquire方法请参考上边 1

 4、lockInterruptibly()方法

          该方法的功能也是获取锁资源,但在获取锁资源之前会先检查当前线程线程是否被中断,

          若当前线程已经被中断,则抛出中断异常退出。

          注意:

                  该方法在获取锁资源失败时,会一直等待,直到当前线程被唤醒或被中断;

                  若被中断会异常退出。

          lockInterruptibly 方法代码如下:

                

public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }



/*
     * 独占式的同步状态获取(即锁的获取),响应中断,即该在等待获取同步状态时,若
     * 当前线程被中断了,则立即抛出 InterruptedException 异常
     */
public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())//首先检查中断状态
            throw new InterruptedException();
        /*
         * 当前线程尝试获取锁,若成功了获取锁,则直接结束,否则执行方法 doAcquireInterruptibly(arg) ,先
         * 把当前线程入队列,然后在 阻塞和非阻塞 之间连续变换,直到 tryAcquire() 方法执行成功为止
         */
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }


 /**
     * 同步可中断模式下同步状态(即锁)的获取操作
     */
private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        //采用给定的模式,为当前线程创建一个节点,并把节点添加到队列中,返回新创建的节点
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            //死循环,是一个 “自循环” 的过程
            for (;;) {
                //获取 node 的前置节点
                final Node p = node.predecessor();
                //只有node 的前置节点为头节点,当前线程才能够获取锁成功
                //若当前线程成功了获取锁,则退出自循环
                if (p == head && tryAcquire(arg)) {
                    //设置头节点
                    setHead(node);
                    //删除原先的头节点
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                //若当前线程获取锁失败,且当前线程已经阻塞,若中断阻塞状态下的当前线程,则直接抛出异常
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }




/**
     * 判断当前线程阻塞方式:park 或 interrupted
     * 根据中断方式可以知道唤醒当前阻塞线程的方式
     */
private final boolean parkAndCheckInterrupt() {
        /**
         * 这里的this指代当前AQS对象,表示当前线程阻塞在那个对象上,后期可以通过jstack来看到,用于排查问题
         */
        LockSupport.park(this);//阻塞当前线程
        //判断是否以中断的方式来唤醒当前阻塞(挂起)的线程,还是以正常方式唤醒当前(挂起)线程
        //若是中断唤醒,则返回true,否则返回false
        //唤醒线程方式:1、interrupt,2、unpark
        return Thread.interrupted();//
    }

五、ReetrantLock释放锁的过程

       线程调用ReetrantLock的unlock() 方法释放锁时,就是调用tryRelease()方法去释放锁;

       释放锁时,首先判断当前线程是否持有锁资源,若当前线程没有持有锁资源,则直接抛出

       异常。

        tryRelease() 方法的参数 releases 表示释放锁的次数(该参数一般默认为1),当释放锁

        时,会拿当前线程状态 state - releases,若结果为0,表示释放锁成功,否则释放锁失败;

        释放锁成功后唤醒AQS中的后继有效的节点。

         ReetrantLock 释放锁代码如下:

           

/**
   ReetrantLock 方法
*/
public void unlock() {
        sync.release(1);
    }



/**
     * 独占式同步状态释放(即锁释放)
     *
     * 若tryRelease() 方法返回true,即当前线程成功了释放同步状态,则会通过unparkSuccessor(Node)方法
     * 唤醒头节点的后继节点线程,该方法最终调用了LockSupport.unpark(Thread)方法来唤醒处于等待状态线程
     */
public final boolean release(int arg) {
        //释放写锁
        if (tryRelease(arg)) {//子类判定释放锁是否成功
            //子类释放锁成功后成,检查阻塞队列唤醒即可
            Node h = head;
            //若头节点不为null,且头节点未被取消(即头节点的状态不为0),则调用unparkSuccessor() 方法唤醒
            //头节点的后序节点,然后返回true
            if (h != null &&            //若头节点h=null,表示AQS队列从来没有被使用过
                    h.waitStatus != 0)  //头节点若没有被取消,即状态值不为0,那么头节点状态值一定是SIGNAL=-1,是活跃的节点
                //头节点是有效的节点,且标注需要唤醒后继节点,那么唤醒即可
                unparkSuccessor(h);
            return true;
        }
        return false;
    }



/**
         * 释放锁,公平锁与非公平锁公用方法,释放锁并不区分是否是非公平锁
         * 内部类 Sync 的方法
         */
protected final boolean tryRelease(int releases) {
            //计算新的同步状态,即锁的状态
            int c = getState() - releases;
            //若持有锁的线程不是当前线程,即当前线程没有持有锁,则抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            //锁释放标志
            boolean free = false;
            //若新的状态c 为0,则表示没有线程持有锁,即释放锁成功
            //并设置线程持有者为null,即没有线程持有锁,锁释放成功了
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //重入锁,设置新的同步状态(锁状态)
            //注意:此时全局变量state 的值没有改变,也即表明在setState之前,没有别的线程能够获取锁
            //这时保证了上面 if 块的原子性
            setState(c);
            //free=true 表示锁释放成功了,AQS可以唤醒阻塞队列中正在等待的线程
            return free;
        }


      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值