type
status
date
slug
summary
tags
category
icon
password
Property
Feb 7, 2025 09:43 AM
本文大部分内容来自作者通过LLM+网盘爬虫获取到的Java相关系列教程:图灵架构第六期内容

JUC并发工具类与常见业务场景

sychronize锁的缺点:不能自己控制锁释放;是一把非公平锁;
他引入cas自旋轻量级所的原因就是为了避免每次操作都去获取monitor锁,这会消耗性能。

一、ReentrantLock锁

ReentrantLock是一种可重入的独占锁,它允许同一个线程多次获取同一个锁而不会被阻塞。 它的功能类似于synchronized是一种互斥锁,可以保证线程安全。相对于 synchronizedReentrantLock经常搭配Condition一起使用。并且,ReentrantLock具备如下特点:
  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量
  • synchronized 一样,都支持可重入
主要应用场景:在多线程环境下对共享资源进行独占式访问,以保证数据的一致性和安全性。

1.1 ReentrantLock 规范

ReentrantLock实现了Lock接口规范,常见API如下:
notion image
常见用法 1:
常见用法 2:
注意事项:
  1. ReentrantLock默认是非公平锁
notion image
ReentrantLock是支持公平锁和非公平锁的。
  • 公平锁:线程在获取锁时,按照等待的先后顺序获取锁。
  • 非公平锁:线程在获取锁时,不按照等待的先后顺序获取锁,而是随机获取锁。ReentrantLock默认是非公平锁
公平与非公平可参考来自网络插图:
notion image
  1. ReentrantLock是可重入锁 可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。在实际开发中,可重入锁常常应用于递归操作、调用同一个类中的其他方法、锁嵌套等场景中。
  1. 加锁次数和释放锁次数一定要保持一致,否则会导致线程阻塞或程序异常;
  1. 加锁操作一定要放在 try 代码之前,这样可以避免未加锁成功又释放锁的异常;对于为什么一定要将lock操作至于try以外,我通过查阅了相关资料之后,综合一下解释:
      • lock()必须位于try块之外,因为假设lock()执行成功,之后无论如何(异常抛出与否)都要执行unlock。
      • 如果将lock()放在try块之内,那么在lock()获得锁之前如果发生异常(比如某些运行时异常),代码会进入finally块并执行unlock(),此时并未持有锁,导致抛出IllegalMonitorStateException。因此,必须把lock()放在try块之外,而unlock放在finally里面,确保只有成功获取锁之后才会执行解锁操作。
  1. 释放锁一定要放在 finally 中,否则会导致线程阻塞。
ReentrantLock总结
  1. 解决多线程竞争资源的问题,例如多个线程同时对同一个数据库进行写操作,可以使用ReentrantLock保证每次只有一个线程能够写入。
  1. 实现多线程任务的顺序执行,例如在一个线程执行完某个任务后,再让另一个线程执行任务。
  1. 实现多线程等待/通知机制,例如在某个线程执行完某个任务后,通知其他线程继续执行任务。

1.2 Condition 规范

java.util.concurrent类库中提供Condition类来实现线程之间的协调。调用Condition.await() 方法使线程等待,其他线程调用Condition.signal() 或 Condition.signalAll() 方法唤醒等待的线程。注意:如果与ReentranLock一同使用的时候,调用Conditionawait()signal()方法,都必须在lock保护之内,否则会出现异常;

二、Semaphore

Semaphore(信号量)是一种用于多线程编程的同步工具,用于控制同时访问某个资源的线程数量。
notion image
Semaphore维护了一个计数器,线程可以通过调用acquire()方法来获取Semaphore中的许可证,当 计数器为0时,调用acquire()的线程将被阻塞,直到有其他线程释放许可证;线程可以通过调用 release()方法来释放Semaphore中的许可证,这会使Semaphore中的计数器增加,从而允许更多的 线程访问共享资源

2.1 常用API

notion image
参数解释:其中permits 表示许可证的数量(资源数),fair 表示公平性,如果这个设为 true 的话,下次执行的线程会是等待最久的线程。
常用函数
  • acquire() 表示阻塞并获取许可
  • tryAcquire() 方法在没有许可的情况下会立即返回 false,要获取许可的线程不会阻塞
  • release() 表示释放许可

2.2 基本案例

代码案例,请求限流:
Semaphore实现数据库连接池案例:

2.3 应用场景总结:

  • 限流:Semaphore可以用于限制对共享资源的并发访问数量,以控制系统的流量。
  • 资源池:Semaphore可以用于实现资源池,以维护一组有限的共享资源。

三、CountDownLatch

CountDownLatch(闭锁)是一个同步协助类,允许一个或多个线程等待,直到其他线程完成操作 集。CountDownLatch使用给定的计数值(count)初始化。await方法会阻塞直到当前的计数值 (count),由于countDown方法的调用达到0,count为0之后所有等待的线程都会被释放,并且随 后对await方法的调用都会立即返回。这是一个一次性现象 —— count不会被重置。
notion image
 

3.1 常用API

notion image

3.2 CountDownLatch使用

多任务完成后合并汇总

3.3 应用场景总结

  • 并行任务同步:CountDownLatch可以用于协调多个并行任务的完成情况,确保所有任务都完成后再继续执行下一步操作。
  • 多任务汇总:CountDownLatch可以用于统计多个线程的完成情况,以确定所有线程都已完成工作。
  • 资源初始化:CountDownLatch可以用于等待资源的初始化完成,以便在资源初始化完成后开始使用。

四、CyclicBarrier

CyclicBarrier(回环栅栏或循环屏障),是 Java 并发库中的一个同步工具,通过它可以实现让一组线 程等待至某个状态(屏障点)之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后, CyclicBarrier可以被重复使用。
notion image

4.1 常用API

notion image
常用方法

4.2 CyclicBarrier使用

模拟人满发车,利用CyclicBarrier的计数器能够重置,屏障可以重复使用的特性,可以支持类似“人满发车”的场景
多线程批量处理数据

4.4 应用场景总结

  • 多线程任务:CyclicBarrier 可以用于将复杂的任务分配给多个线程执行,并在所有线程完成工作后触发后续操作。
  • 数据处理:CyclicBarrier 可以用于协调多个线程间的数据处理,在所有线程处理完数据后触发后续操作。

4.5 CyclicBarrier 与 CountDownLatch 区别

  • CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的
  • CountDownLatch 参与的线程的职责是不一样的,有的在倒计时,有的在等待倒计时结束。CyclicBarrier 参与的线程职责是一样的。

五、Exchanger

Exchanger是一个用于线程间协作的工具类,用于两个线程间交换数据。具体交换数据是通过 exchange方法来实现的,如果一个线程先执行exchange方法,那么它会同步等待另一个线程也执行 exchange方法,这个时候两个线程就都达到了同步点,两个线程就可以交换数据。
notion image

5.1 常用API

  • V exchange(V v):等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。
  • V exchange(V v, long timeout, TimeUnit unit):等待另一个线程到达此交换点,或者当前线程被中断——抛出中断异常;又或者是等候超时——抛出超时异常,然后将给定的对象传送给该线程,并接收该线程的对象。

5.2 Exchanger使用

模拟交易场景
模拟对账场景

5.3 应用场景总结

  1. 数据交换:在多线程环境中,两个线程可以通过 Exchanger 进行数据交换。
  1. 数据采集:在数据采集系统中,可以使用 Exchanger 在采集线程和处理线程间进行数据交换

六、Phaser

Phaser(阶段协同器)是一个Java实现的并发工具类,用于协调多个线程的执行。它提供了一些方便 的方法来管理多个阶段的执行,可以让程序员灵活地控制线程的执行顺序和阶段性的执行。Phaser可 以被视为CyclicBarrier和CountDownLatch的进化版,它能够自适应地调整并发线程数,可以动态地 增加或减少参与线程的数量。所以Phaser特别适合使用在重复执行或者重用的情况。
notion image

6.1 常用API

  • Phaser(): 参与任务数0
  • Phaser(int parties) :指定初始参与任务数
  • Phaser(Phaser parent) :指定parent阶段器, 子对象作为一个整体加入parent对象, 当子对象中没有参与者时,会自动从parent对象解除注册
  • Phaser(Phaser parent,int parties) : 集合上面两个方法
增减参与任务数方法
  • int register() 增加一个任务数,返回当前阶段号。
  • int bulkRegister(int parties)增加指定任务个数,返回当前阶段号。
  • int arriveAndDeregister() 减少一个任务数,返回当前阶段号。
到达、等待方法
  • int arrive() 到达(任务完成),返回当前阶段号。
  • int arriveAndAwaitAdvance() 到达后等待其他任务到达,返回到达阶段号。
  • int awaitAdvance(int phase) 在指定阶段等待(必须是当前阶段才有效)
  • int awaitAdvancelnterruptibly(int phase) 阶段到达触发动作
  • int awaitAdvancelnterruptiBly(int phase, long timeout, TimeUnit unit)
  • protected booleanonAdvance(int phase,int registeredParties) 类似CyclicBarrier的触发命令,通过重写该方法来增加阶段到达动作,该方法返回true将终结Phaser对象。

6.2 Phaser使用

多线程批量处理数据
阶段性任务:模拟公司团建

6.3 应用场景总结

  • 多线程任务分配:Phaser 可以用于将复杂的任务分配给多个线程执行,并协调线程间的合作。
  • 多级任务流程:Phaser 可以用于实现多级任务流程,在每一级任务完成后触发下一级任务的开始。
  • 模拟并行计算:Phaser 可以用于模拟并行计算,协调多个线程间的工作。
  • 阶段性任务:Phaser 可以用于实现阶段性任务,在每一阶段任务完成后触发下一阶段任务的开始。
相关文章
计算机视觉(一):深度学习的人脸应用
Lazy loaded image
计算机视觉(二):特征向量计算
Lazy loaded image
计算机视觉(三):人脸识别之特征提取
Lazy loaded image
Flowable(一):Java知识学习
Lazy loaded image
Flowable(二):数据库篇
Lazy loaded image
Flowable(三):Liquibase模式管理
Lazy loaded image
JUC核心篇(四):CAS与AQS技术杂文(十一):你真的有理解计算机素养吗?
Loading...
fntp
fntp
多一点兴趣,少一点功利
最新发布
作为Java程序员,最爱的一款字体推荐
2025-2-19
SpringBoot3单机Kafka服务
2025-2-19
Java小知识合集
2025-2-14
Spring笔记整理(一):InitializingBean的妙用
2025-2-7
JUC核心篇(五):JUC并发工具类的使用
2025-2-7
JUC核心篇(四):CAS与AQS
2025-2-7
公告
📝 博客只为了记录我的学习生涯
😎 我的学习目标是成为一名极客
🤖 我热爱开源当然我也拥抱开源
💌 我期待能收到你的Email留言
📧 我的邮箱:stickpoint@163.com
欢迎交流~