ReentrantLock获取锁释放锁的流程示例分析

 更新时间:2022年11月23日 16:40:40   作者:Alan_YYL  
这篇文章主要为大家介绍了ReentrantLock获取锁释放锁的流程示例分析详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

目的

  • 了解ReentrantLock获取锁、释放锁的流程

代码

package com.company.aqs;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * ReentrantLock使用案例——使用ReentrantLock加锁
 * @Author: Alan
 * @Date: 2022/11/20 01:38
 */
public class ReentrantLockDemo {
    private static int sum=0;
    private static Lock lock=new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new Thread(()->{
                // 获取锁
                lock.lock();
                try {
                    for (int j = 0; j < 1000; j++) {
                        sum++;
                    }
                }finally {
                    // 在finally代码块中释放锁
                    lock.unlock();
                }
            }).start();
        }
        // 保证所有线程执行完毕
        Thread.sleep(1000);
        System.out.println(sum);
    }
}

这是一个使用ReentrantLock实现多线程求和的案例。代码逻辑比较简单,外层循环开启了3个线程,然后每个线程内多sum累加1000,最后输出结果sum=1000。

获取锁流程

整个过程概括起来就做了两件事儿

  • 获取锁成功,执行当前线程内的其他事情;
  • 获取锁失败,当前线程加入同步队列,同时阻塞当前线程。

当第一个线程(thead0)进来的时候,通过CAS去修改state属性为1,如果成功,通过setExclusiveOwnerThread()方法设置exclusiveOwnerThread为当前线程

此时,第二个线程(thead1)进来,再去通过CAS修改state属性为1时,便会失败,此时进入acquire()方法。最终会走到如下方法,首先通过tryAcquire()方法再次尝试去获取锁。

tryAcquire()方法内部还是会通过CAS去获取锁。此时锁资源还被第二线程持有,因此会返回false。现在接着看acquire()方法中的if判断。

此时,会进行acquireQueued(addWaiter(Node.EXCLUSIVE), arg))判断。这里需要执行两个方法addWaiter()和acquireQueued()。首先看addWaiter()方法。这个方法,我们需要关注以下四点。

  • 首先会先构造一个node节点(节点内部细节,可以看其构造方法)。
  • 如果tail(同步队列尾节点指针)不为空,其实也就是同步队列不为空,那么就把第1步构建的节点通过尾插法加入队列中,然后返回

如果同步队列为空了,那么执行enq()方法,这个方法为我们做了两件事儿。

  • 如果同步队列为空,那么初始化队列
  • 队列初始化完成后,将node节点入队。

通过addWaiter()方法和enq()方法,我们也可以看出来,AQS中的同步队列是通过双向链表来实现的,节点入队和出队,需要修改两个指针才行(prev和next)。

addWaiter()方法执行执行完毕后,我们通过下面这张图大致看下此时同步队列中的节点指向情况。此处,不太理解可以再回头看看enq()方法的执行流程。

addWaiter()方法执行执行完毕后,会返回入队后的新节点。然后开始执行acquireQueued()方法。这个方法做了五件事儿。

  • 获取当前节点的前驱节点p
  • 如果p是头节点,那么再次尝试去获取锁,获取锁成功,就可以跳出循环
  • 获取锁失败,通过shouldParkAfterFailedAcquire()去修改waitSatus为-1(为什么修改为-1,这里可以从AQS的源码中找到原因,后续获取锁的流程也会遵从这个逻辑)

4. parkAndCheckInterrupt()方法会阻塞当前线程,同时,能够返回当前线程的中断状态(Thread.interrupted()会清除中断标记位)。

经过上述的一通操作,我们可以知道,线程1没有获取到锁,被加入到了同步队列,并且还对其进行了阻塞。概括下就是四个字“入队”、“阻塞”。此时我们的同步队列也变成如下所示。和上述执行完addWaiter()方法相比,只是线程1节点的前驱节点,waitStaus被设置成了-1。

释放锁流程

整个过程(只考虑释放锁成功)概括起来做了三件事儿

  • 释放锁资源;
  • 唤醒同步队列中头节点的后一个节点对应的线程
  • 步骤2被唤醒的该线程尝试竞争锁,竞争锁成功,那么更新同步队列(即头节点出队)。

当第一个线程(thread0)执行完自己的业务流程后,就会释放锁。此时,我们来看看释放锁的流程又是什么样子的。调用unlock()方法可以对锁进行释放,需要注意的时,为了避免死锁,需要将该方法的调用放在fiaally代码块中。

当thread0去释放锁时,会调用release()方法,该方法主要做了两件事儿。

  • 调用tryRelease()方法释放锁

其方法内部,会将state设置为0,同时通过setExclusiveOwnerThread()方法设置exclusiveOwnerThread为当null,代表此时锁资源被释放,没有任何线程持有锁资源

如果第一步返回true,即释放锁成功,那么开始第二步。第二步首先获取同步队列的头节点,查看其waitStatus属性。这里我们把刚才获取锁过后,同步队列中的节点情况再放一下。此时,我们的head节点的waitStatus为-1,因此会进入unparkSuccessor()方法

unparkSuccessor()方法,主要做了以下三件事儿。注意第三步,根据我们当前同步队列的情况来看,LockSupport.unpark()方法会唤醒线程1。

当执行完unparkSuccessor()方法中的LockSupport.unpark()方法后,线程1(thread1)就会被唤醒了。线程1被唤醒过后,我们又来看看线程1的执行情况。此时线程1,会继续执行acquireQueued()方法中的for循环(注意:这是一个死循环哦)。执行顺序和获取锁时是一致的,和获取锁有所不同的时,此时执行第2步获取锁是会成功的(因为thread0已经释放锁了)。

获取锁成功后呢,会让当前同步队列中的头节点出队,此时,同步队列中的节点情况如下所示。

此时,释放锁的逻辑就执行完成了。归纳起来其实也很简单,首先释放锁资源,然后再唤醒同步队列中头节点的后一个节点对应的线程,最后,更新同步队列(出队)。

总结

以上便是ReentrantLock获取锁、释放锁的的大致流程。通过这篇文章,读者对ReentrantLock的获取锁、释放锁过程有一个大致的了解了,细心的读者可能会发现,获取锁时acquireQueued()方法中有一个队cancelAcquire()方法的调用逻辑,这里没有详细解释,博主会在后面的文章中详细来解释这个方法的处理逻辑(flag先立下!!!)。

以上就是ReentrantLock获取锁释放锁的流程示例分析的详细内容,更多关于ReentrantLock获取锁释放锁的资料请关注脚本之家其它相关文章!

相关文章

  • 如何实现Java的ArrayList经典实体类

    如何实现Java的ArrayList经典实体类

    ArrayList是Java集合框架中一个经典的实现类。他比起常用的数组而言,明显的优点在于,可以随意的添加和删除元素而不需考虑数组的大小。下面跟着小编一起来看下吧
    2017-02-02
  • 浅谈Spring与SpringMVC父子容器的关系与初始化

    浅谈Spring与SpringMVC父子容器的关系与初始化

    这篇文章主要介绍了浅谈Spring与SpringMVC父子容器的关系与初始化,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • MyBatis源码分析之日志logging详解

    MyBatis源码分析之日志logging详解

    这篇文章主要给大家介绍了关于MyBatis源码分析之日志logging的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-03-03
  • 深入剖析Java中String类的concat方法

    深入剖析Java中String类的concat方法

    这篇文章主要介绍了Java中String类的concat方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • Java面试题冲刺第十二天--数据库(2)

    Java面试题冲刺第十二天--数据库(2)

    这篇文章主要为大家分享了最有价值的三道数据库面试题,涵盖内容全面,包括数据结构和算法相关的题目、经典面试编程题等,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • SSH框架实现表单上传图片实例代码

    SSH框架实现表单上传图片实例代码

    本篇文章主要介绍了SSH框架实现表单上传图片实例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • 使用@Builder导致无法创建无参构造方法的解决

    使用@Builder导致无法创建无参构造方法的解决

    这篇文章主要介绍了使用@Builder导致无法创建无参构造方法的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • IntelliJ IDEA中使用mybatis-generator的示例

    IntelliJ IDEA中使用mybatis-generator的示例

    这篇文章主要介绍了IntelliJ IDEA中使用mybatis-generator,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-04-04
  • 使用java执行定时任务示例

    使用java执行定时任务示例

    这篇文章主要介绍了使用java执行定时任务示例,需要的朋友可以参考下
    2014-04-04
  • JVM中的GC初识

    JVM中的GC初识

    GC(Garbage Collection)称之为垃圾回收,是对内存中的垃圾对象,采用一定的算法进行内存回收的一个动作,这篇文章主要介绍了JVM中的GC初识,需要的朋友可以参考下
    2022-05-05

最新评论