ReentrantLock中四种加锁方式的使用区别和源码实现的细节差异

1.概述

在上一篇博客当中,详细的介绍了ReentrantLock的加锁和解锁的过程,但还遗留了一些问题。本文将介绍ReentrantLock当中4个加锁方式的使用区别和分析其源码实现的细节差异。

2.使用区别

  • lock()

    不可被中断的获取锁的方式。

  • tryLock()

    尝试一次拿锁,拿不到就返回false,不会像其他方式一样进入阻塞队列。如果拿到了就返回true。

  • lockInterruptibly()

    可被中断的获取锁的方式,在拿锁的期间,如果被中断了,那么会抛出异常,取消拿锁。

  • tryLock(long,TimeUnit)

    在一定的时间范围内,如果该线程没有被中断,可以尝试的去获取锁。如果超过了预期的时间范围,就算锁无人占用也无法拿到锁。和上面一样同样会因为中断,而抛出异常,取消拿锁。

3.源码实现的细节差异

lock方法在上一篇博客当中已经介绍的很详细了,这里就不再赘述。下面主要分析其余三个方法是如何实现的。

  • tryLock()

    该方法的实现原理比较简单。
    在这里插入图片描述
    内部调用Sync的nonfairTryAcquire方法,仅仅尝试一次获取锁,拿到了返回true,拿不到返回false。在这里插入图片描述

  • lockInterruptibly()在这里插入图片描述
    调用Sync的acquireInterruptibly方法,来看看该方法是如何实现的。在这里插入图片描述
    首先判断该线程是否被中断过,如果被中断过则重置打断标记,并返回true。此时就会抛出中断异常。

    如果没有被中断过,那么此时就会调用tryAcquire方法尝试的获取锁。该方法也就是走上一篇博客当中分析过的公平锁或者非公平锁的尝试加锁的逻辑。拿锁成功则结束。

    拿锁失败会调用doAcquireInterruptibly方法。在这里插入图片描述
    左边是lock方法之后会执行到的逻辑,右边是lockInterruptibly方法的逻辑

    差异很小,只是对于发生中断的线程的处理方式不一样。lock当中仅仅将发生中断的线程设置一个标记,并在之后以补偿的机制重新中断,并不会影响线程继续去拿锁。lockInterruptibly会抛出异常。那么此时failed标记为true,就是执行cancelAcquire方法,取消线程拿锁的操作。

  • tryLock(long,TimeUnit)在这里插入图片描述
    将long型数值转换成纳秒,然后Sync的tryAcquireNanos方法在这里插入图片描述
    可以看到该方法也是会被中断影响的,如果线程被中断了,则抛出异常。

    否则调用tryAcquire 尝试拿锁,没有拿到的话继续调用doAcquireNanos方法,重点来看看doAcquireNanos方法的实现。在这里插入图片描述
    首先,如果传入的时间小于0 ,那么直接返回false

    然后根据传入的时间,计算出拿锁的截止时间,表示只有在这个截止日期之前可以尝试拿锁。结点入队。

    之后就是死循环,结点的前驱结点是队头,可以尝试拿锁。拿到锁返回true。否则计算剩余时间,如果剩余时间小于0,那么就表示超时啦,拿锁失败。

    如果没有超时,那么就调用shouldParkAfterFailedAcquire判断是否需要阻塞,如果返回true,还需要判断剩余的时间是否大于spinForTimeoutThreshold。因为时间太短的话,阻塞没有意义,过一会儿又要被唤醒了。上面两个条件都满足的话,调用parkNaons阻塞一定的时间。

    如果线程被中断过,那么就抛出中断异常,接着会进入finally块,failed为true 则取消该线程拿锁。

猜你喜欢

转载自blog.csdn.net/gongsenlin341/article/details/112982661