【JUC】并发编程 AQS,ReentryLock,CyclicBarrier,CountDownLatch 原理总结

本文主要是介绍【JUC】并发编程 AQS,ReentryLock,CyclicBarrier,CountDownLatch 原理总结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

AQS

AQS是什么?重写AQS就能实现锁的效果?

AQS是一个抽象类,是一个并发包的基础组件,用来实现各种锁,同步组件的工具(通过volatile + cas进行实现)。它包含了共享成员变量state、等待队列、条件队列、加锁线程 并发中的核心组件。

共享成员变量state,不同实现中有不同含义。

等待队列,基于Node内部类,实现了一个双向链表。

条件队列,基于Node内部类,实现了一个单项链表,相当于Synchronized的wait和notify的一个等待唤醒机制的条件队列。

AQS自己继承了 AbstractOwnableSynchronizer,owner就是锁的持有者,对于线程信息的封装。

AQS还为我们自己实现锁和同步器采用模板方法模式,提供了一些模板方法。我们只需要根据自己的逻辑实现方法的重写,就可以实现各种不同的互斥/同步等效果。

例如:ReentrantLock 可重入锁的实现,阻塞 加锁 解锁 等操作都是基于 ReentrantLock 内部的AQS组件实现的,本质上ReentrantLock只是提供了一系列相关的API。(Semaphore,CountDownLatch,CyclicBarrier,Renentrantreadwritelock,StampedLock)

AQS有两种模式:独占模式(ReentrantLock CycleBarrier)和 共享模式(Semaphore CountDownLatch)

独占和共享的最大区别就是State的定义不同,独占模式下State只有0和1,共享资源/临界区代码 只能由一个线程来执行,但是共享模式下的State可以为多个,只要是符合条件的当前线程都可以来使用。

补充: AQS的阻塞队列和条件队列的实现,都是通过Node节点,不过是通过Node节点的不同属性,且一个是双向 一个是单向

在这里插入图片描述

阻塞队列双向的条件队列单向?

  • 阻塞队列中被park( ) 的线程是需要由前继节点老unpark( ) 唤醒的。
    • 当node加入到阻塞队列尾部,需要找到前一个节点,把它的waitStatus设置成 -1(表示它有责任唤醒后一个节点)
  • 条件队列是由别的线程来signal( ) 唤醒的,且唤醒后会去阻塞队列中。
    • 条件队列是FIFO,尾进头出的,不需要双向。

ReentryLock

在这里插入图片描述

lock,unlock

lock

  1. 加锁成功,将state改为1,设置owner为当前线程
  2. 加锁失败
    1. 创建该线程对应的Node节点,并加入等待队列,此时waitStatus为0(默认值)
    2. 会将加入队列的该线程调用Lock。unpark( ) 来阻塞,然后设置该节点的前驱节点的waitStatus为-1(用于后续unpark( ) 它)

unlock

  1. 加锁成功的线程要解锁后会unlock( ) 掉阻塞队列中第一个节点的线程,等待队列中出来的线程获取锁成功

    1. setOwner为自己
    2. 设置state为1
    3. 更新等待队列
  2. 当unlock后,如果被unpark( ) 的线程获取锁失败,重新回到等待队列中,并park( ) 掉

锁重入

lock

  1. 第一次会正常加上锁,setOwner是自己,然后修改state为1。
  2. 第二次同一个线程又来加锁了,会检查到当前线程=owner线程,说明发生了锁重入现象。
  3. 然后会对state++,做一个累加操作,作为锁重入的计数。

unlock

  1. state–;
  2. 只是一直对state–,并没有真正的释放锁,当state==0时,说明才真的该释放锁。
  3. state==0 时再执行unlock方法流程。

可打断

  • ReentrantLock分为不可打断模式 和 可打断模式: lock.lock() 不可被打断 lock.lockInterruptibly(); 可被打断
  • 使用时在API使用的不同选择的就是不同模式的加锁解锁方式

区别:

  • 不可打断模式没有真的打断,只是设置打断标记为true。还是继续停留在等待队列中等待。当获取到锁之后才检查是否被打断,再进行打断。
  • 可打断模式打断了,通过抛出异常的方式保证当前线程被打断。

公平,非公平

非公平锁(默认)和公平锁的主要区别在于 tryAcquire( ) 的实现。【尝试获取锁的方法】

当 state == 0 后的操作不同!

  • 非公平锁一上来不会看等待队列中是否有阻塞等待的线程,而是直接去cas操作去判断state来竞争锁
  • 公平锁一上来不会直接cas操作获取修改state,而是先判断等待队列中是否有优先级比我高的队列,实现了公平

相对来说,非公平锁会更好的性能,因为它的吞吐量比较大。

当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

在这里插入图片描述

公平构造为 FairSync()

条件变量

每个条件变量其实对应着一个等待队列,其实现类是ConditionObject。

ConditionObject中维护了以Node为节点的双向链表所构成的队列。但是只使用了单向!

Await( )

  1. 首先获取到锁,然后条件不满足时调用await( ) 。
    • 此时创建一个新的Node状态为 -2,并联这个不满足条件的线程,加入条件队列的尾部。
  2. 进入AQS的fullRelease,释放掉同步器上的锁。
    1. 设置owner为null
    2. setState = 0
    3. unpark( ) 等待队列中的线程
  3. 去条件变量中等待的线程也会被park( ) 进入阻塞状态

Signal( )

(有一个节点的转移,会条件队列中获取队头元素转移到等待队列队尾,并重置waitStatus=0)

只有owner中的线程才有资格唤醒条件变量中的所有者!

  1. 取得在条件变量中(队首)的第一个Node
  2. 会将满足条件的该线程转移到等待队列中等待下次重新获取锁。
  3. 并将当前线程状态从-2改为0。因为等待队列中每次增加的元素都默认是0。

在这里插入图片描述

锁超时

public boolean tryLock():尝试获取锁,获取到返回 true,获取不到直接放弃,不进入阻塞队列

public boolean tryLock(long timeout, TimeUnit unit):在给定时间内获取锁,获取不到就退出

实现原理

  • 成员变量:指定超时限制的阈值,小于该值的线程不会被挂起,会自旋

    static final long spinForTimeoutThreshold = 1000L;
    

    超时时间设置的小于该值,就会被禁止挂起,因为阻塞在唤醒的成本太高,不如选择自旋空转

  • tryLock()

    sync.nonfairTryAcquire(1);// 只尝试一次
    
  • tryLock(long timeout, TimeUnit unit)

    //先尝试一次nonfairTryAcquire()后doAcquireNanos(arg, nanosTimeout);
    // 获取最后期限的时间戳
    // 计算还需等待的时间
    // 时间已到     return false;
    // 如果 nanosTimeout 大于该值,才有阻塞的意义,否则直接自旋会好点
    

ReentrantLock 对比 Synchronized

ReentrantLock 相对于 synchronized 具备如下特点:

  1. 锁的实现:synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的
  2. 性能:新版本 Java 对 synchronized 进行了很多优化,synchronized 与 ReentrantLock 大致相同
  3. 使用:ReentrantLock 需要手动解锁,synchronized 执行完代码块自动解锁
  4. 可中断:ReentrantLock 可中断,而 synchronized 不行
  5. 公平锁:公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁
    • ReentrantLock 可以设置公平锁,synchronized 中的锁是非公平的
    • 不公平锁的含义是阻塞队列内公平,队列外非公平
  6. 锁超时:尝试获取锁,超时获取不到直接放弃,不进入阻塞队列
    • ReentrantLock 可以设置超时时间,synchronized 会一直等待
  7. 锁绑定多个条件:一个 ReentrantLock 可以同时绑定多个 Condition 对象,更细粒度的唤醒线程
  8. 两者都是可重入锁

C​y​cli​c​B​arr​i​er​ 对比 Count​Do​wn​La​t​ch​​

使用上的区别就是 CountDownLatch 的计数只能使用一次,CyclicBarrier在计数变为0之后,会重置计数!

  1. 等待主体不同。调用await( ) 方法的对象不同。
    1. CountDownLatch是主线程调用await( ) 来等待其他线程将state减为0再来执行。阻塞的是主线程。
    2. CyclicBarrier是工作线程调用await( ) ,await( ) 方法会对自身维护的计数器 -1 操作。阻塞的是工作线程。
      • 多组线程等待共同到达一个栅栏点,通过 signalAll( ) 一起出来,并且把 count 重新置为 parties。
  2. CountDownLatch是通过AQS的State信号量来实现的,而CyclicBarrier是直接借助ReentrantLock加上Condition 等待唤醒的功能 进而实现的。

在这里插入图片描述

这篇关于【JUC】并发编程 AQS,ReentryLock,CyclicBarrier,CountDownLatch 原理总结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/971337

相关文章

Python实现图片分割的多种方法总结

《Python实现图片分割的多种方法总结》图片分割是图像处理中的一个重要任务,它的目标是将图像划分为多个区域或者对象,本文为大家整理了一些常用的分割方法,大家可以根据需求自行选择... 目录1. 基于传统图像处理的分割方法(1) 使用固定阈值分割图片(2) 自适应阈值分割(3) 使用图像边缘检测分割(4)

Windows Docker端口占用错误及解决方案总结

《WindowsDocker端口占用错误及解决方案总结》在Windows环境下使用Docker容器时,端口占用错误是开发和运维中常见且棘手的问题,本文将深入剖析该问题的成因,介绍如何通过查看端口分配... 目录引言Windows docker 端口占用错误及解决方案汇总端口冲突形成原因解析诊断当前端口情况解

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

C#中async await异步关键字用法和异步的底层原理全解析

《C#中asyncawait异步关键字用法和异步的底层原理全解析》:本文主要介绍C#中asyncawait异步关键字用法和异步的底层原理全解析,本文给大家介绍的非常详细,对大家的学习或工作具有一... 目录C#异步编程一、异步编程基础二、异步方法的工作原理三、代码示例四、编译后的底层实现五、总结C#异步编程

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

Go 语言中的select语句详解及工作原理

《Go语言中的select语句详解及工作原理》在Go语言中,select语句是用于处理多个通道(channel)操作的一种控制结构,它类似于switch语句,本文给大家介绍Go语言中的select语... 目录Go 语言中的 select 是做什么的基本功能语法工作原理示例示例 1:监听多个通道示例 2:带

鸿蒙中@State的原理使用详解(HarmonyOS 5)

《鸿蒙中@State的原理使用详解(HarmonyOS5)》@State是HarmonyOSArkTS框架中用于管理组件状态的核心装饰器,其核心作用是实现数据驱动UI的响应式编程模式,本文给大家介绍... 目录一、@State在鸿蒙中是做什么的?二、@Spythontate的基本原理1. 依赖关系的收集2.

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.