JUC常见类

news/2025/2/8 12:42:01/

JUC是java.util.concurrent的简称,当中有我们需要熟知的常见类,此文为你带来相关知识

前面我们学习了3种创建线程的方法,分别是:

  1. 继承Thread类,重写run方法
  2. 实现Runnable接口,重写run方法
  3. 创建线程池,利用线程池创建线程

JUC中为我们提供了一个新的创建线程的方法

1. Callable接口

Callable 是⼀个interface.相当于把线程封装了⼀个"返回值".方便程序猿借助多线程的方式计算结果.

那么既然已经有了Runnable接口可以创建线程任务,为何还要Callable接口呢??

我们从一个案例来探究一下吧!


示例: 创建线程计算1+2+3+…+100,不使用Callable版本

  • 创建⼀个类Result,包含⼀个sum表示最终结果,lock表示线程同步使用的锁对象.
  • main方法中先创建Result实例,然后创建⼀个线程t.在线程内部计算1+2+3+…+1000.
  • 主线程同时使用wait等待线程t计算结束.(注意,如果执行到wait之前,线程t已经计算完了,就不
    必等待了).
  • 当线程t计算完毕后,通过notify唤醒主线程,主线程再打印结果.
java">class Result{//累加和int sum=0;// 锁对象public Object lock = new Object();
}
public class TestRunnable {public static void main(String[] args) {Result result=new Result();Thread t=new Thread(){@Overridepublic void run() {int sum = 0;for (int i = 1; i <= 100; i++) {// 执行累加操作sum += i;}// 为结果赋值result.sum = sum;// 唤醒等待的线程synchronized (result.lock) {// 检查累加是否执行完成while (result.sum == 0) {// 没有累加完成,等待结果try {result.lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 打印结果System.out.println(result.sum);result.lock.notify();}}};t.start();}
}

示例: 创建线程计算1+2+3+…+100,使用Callable版本

  • 创建⼀个匿名内部类,实现Callable接⼝.Callable带有泛型参数.泛型参数表示返回值的类型.
  • 重写Callable的call方法,完成累加的过程.直接通过返回值返回计算结果.
  • 把callable实例使用FutureTask包装⼀下.
  • 创建线程,线程的构造方法传入FutureTask.此时新线程就会执行FutureTask内部的Callable的call
    方法,完成计算.计算结果就放到了FutureTask对象中.
  • 在主线程中调用 futureTask.get() 能够阻塞等待新线程计算完毕.并获取到FutureTask中的结果.
java">import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class TestCallable {public static void main(String[] args) throws Exception {//实现Callable接口,重写call()方法Callable <Integer> callable= new Callable<Integer>() {@Overridepublic Integer call() throws Exception {// 执行累加操作int sum=0;for (int i = 0; i <=100; i++) {sum=sum+i;}return sum;}};//Callable要配合FutureTask一起使用,FutureTask用来获取Callable的执行结果FutureTask<Integer> futureTask=new FutureTask<>(callable);// FutureTask当做构造参数传入到Thread构造方法中Thread thread=new Thread(futureTask);thread.start();try {// 等待结果, 的时间可能被中断,会抛出InterruptedExceptionInteger result = futureTask.get();// 打印结果System.out.println("执行结果是:" + result);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();// 打印异常信息System.out.println("打印日志:" + e.getMessage());}}
}

我们发现Callable接口可以返回一个值,并让FutureTask等待结果,不需要借助辅助类,并且不需要使用锁,减少了CPU的消耗


2. Callable和Runnable的差异

我们通过源码来探究其差异:

在这里插入图片描述

总结

  1. Callable要实现call(),且有返回值,Runnable要实现的run()但没有返回值
  2. Callable的call()可以抛出异常,Runnable的run()不能抛出异常
  3. Callable配合FutrueTask一起使用,通过futureTask,get()方法获取call()的结果
  4. 两都是描述线程任务的接口

3. ReentrantLock

可重入互斥锁.和synchronized定位类似,都是用来实现互斥效果,保证线程安全.

ReentrantLock 也是可重⼊锁. Reentrant这个单词的原意就是"可重入"

3.1 ReentrantLock的用法

  • lock(): 加锁,如果获取不到锁就死等.
  • trylock(超时时间):加锁,如果获取不到锁,等待⼀定的时间之后就放弃加锁.
  • unlock(): 解锁

源码:

在这里插入图片描述

java">lock.lock();   
try {    
// 业务逻辑代码   
} finally {   
// 如果业务代码执行到一半抛出了异常,那么会导致释放锁的代码无法执行,所以使用finally
lock.unlock()    
}  

3.2 ReentrantLock 和synchronized 的区别

  • synchronized是⼀个关键字,是JVM内部实现的(大概率是基于C++实现).ReentrantLock是标准
    库的⼀个类,在JVM外实现的(基于Java实现).
  • synchronized使用时不需要⼿动释放锁.ReentrantLock使用时需要⼿动释放.使用起来更灵活,但
    是也容易遗漏unlock.
  • synchronized在申请锁失败时,会死等.ReentrantLock可以通过trylock的方式等待⼀段时间就放弃.
  • synchronized是非公平锁,ReentrantLock默认是非公平锁.可以通过构造方法传入⼀个true开启公平锁模式.实现的过程中有会一个队列来组织排队的线程
    在这里插入图片描述
  • 更强大的唤醒机制.synchronized是通过Object的wait/notify实现等待-唤醒.每次唤醒的是⼀个随机等待的线程.ReentrantLock搭配Condition类实现等待-唤醒,可以更精确控制唤醒某个指定的线程.
java">ReentrantLock lock=new ReentrantLock(true);
// 条件1
Condition male= lock.newCondition();
// 条件2
Condition female= lock.newCondition();
// 根据不同的条件进行阻塞等待
male.await();
male.signal();       // 唤醒相应队列中的一个线程
male.signalAll();   // 唤醒相应队列中的所有线程// 根据不同的条件进行阻塞等待
female.await();
female.signal();    // 唤醒相应队列中的一个线程
female.signalAll(); // 唤醒相应队列中的所有线程
  • 读写锁
java">//创建一个读写锁
ReentrantReadWriteLock readwriteLock=new ReentrantReadWriteLock();
//获取读锁
ReentrantReadWriteLock.ReadLock readLock=readwriteLock.readLock();
//获取写锁
ReentrantReadWriteLock.ReadLock writeLock=readwriteLock.readLock();

3.3 如何选择使用哪个锁?

  • 锁竞争不激烈的时候,使用synchronized,效率更高,自动释放更方便.
  • 锁竞争激烈的时候,使用ReentrantLock,搭配trylock更灵活控制加锁的行为,而不是死等.
  • 如果需要使用公平锁,使用ReentrantLock.

4. 信号量Semaphore

信号量:用来表示"可用资源的个数".本质上就是⼀个计数器.

理解信号量
可以把信号量想象成是饭店的座位牌:当前有座位5个.表示有5个可用资源.
当有人进去的时候,就相当于申请⼀个可用资源,可⽤座位就-1(这个称为信号量的P操作)
当有人出来的时候,就相当于释放⼀个可用资源,可⽤座位就+1(这个称为信号量的V操作)
如果计数器的值已经为0了,还尝试申请资源,就会阻塞等待,直到有其他线程释放资源.

Semaphore的PV操作中的加减计数器操作都是原⼦的,可以在多线程环境下直接使用.

示例: 模拟吃饭的场景

java">// 初始化一个信号量的对象, 指定系统可用资源的数量, 相当于一个饭店有5个餐位Semaphore semaphore=new Semaphore(5);Runnable runnable=new Thread(){@Overridepublic void run() {//1.尝试申请资源,寻找座位吃饭System.out.println(Thread.currentThread().getName()+"尝试申请资源(寻找座位吃饭)");try {semaphore.acquire();} catch (InterruptedException e) {e.printStackTrace();}//2.获取到了资源System.out.println(Thread.currentThread().getName()+"获取到了资源(拥有了座位)");//3.利用资源处理业务try {//休眠一会,模拟业务处理消耗时间System.out.println(Thread.currentThread().getName()+"吃饭ing......");TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//4.释放资源semaphore.release();System.out.println(Thread.currentThread().getName()+"释放了资源(吃完饭走人)");}};//创建20个线程,模拟20个人吃饭场景for (int i = 0; i < 20; i++) {//创建线程并指定任务Thread thread=new Thread(runnable);//启动线程thread.start();}}

说明:

  • acquire用于申请资源
  • release用于释放资源

5. CountDownLatch

同时等待N个任务执行结束.

好像跑步⽐赛,10个选⼿依次就位,哨声响才同时出发;所有选⼿都通过终点,才能公布成绩。

示例: 模拟跑步比赛的场景

java">//指定参赛选手的个数(线程数)CountDownLatch countDownLatch=new CountDownLatch(10);System.out.println("各就各位,预备...");for (int i = 0; i < 10; i++) {Thread player = new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + "开跑.");// 模拟比赛过程, 休眠2秒TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName() + "到达.");// 标记选手已达到终点,让countDownLatch的计数减1, 当计数到0时,表示所有的选手都到达终点,比赛结束countDownLatch.countDown();} catch (InterruptedException e) {e.printStackTrace();}}, "player" + i);// 启动线程player.start();}TimeUnit.MILLISECONDS.sleep(10);System.out.println("===== 比赛进行中 =====");// 等待比赛结束countDownLatch.await();// 颁奖System.out.println("比赛结束, 进行颁奖");

图解:

在这里插入图片描述

说明:

  • countDownLatch的计数减1,当计数到0时,表示所有的选手都到达终点,比赛结束
  • await负责等待计算器为0

应用场景:
一个大的文件,可以分割成好多块,一个线程去下载一个小块,当所有的线程都执行完下载任务,再把各个小块的内容拼成一个完整的文件 (迅雷)


http://www.ppmy.cn/news/1570320.html

相关文章

阿里云平台使用 DeepSeek 模型:完整开发指南

本文将详细介绍如何在阿里云平台上使用 DeepSeek 大语言模型,包括环境配置、基础调用、高级特性以及最佳实践等内容。 一、入门准备 1.1 环境配置 首先需要配置必要的开发环境: # 安装阿里云 DashScope SDK pip install alibabacloud_dashscope# 安装其他依赖包 pip insta…

CSS 伪类(Pseudo-classes)的详细介绍

CSS 伪类详解与示例 在日常的前端开发中&#xff0c;CSS 伪类可以帮助我们非常精准地选择元素或其特定状态&#xff0c;从而达到丰富页面表现的目的。本文将详细介绍以下伪类的使用&#xff1a; 表单相关伪类 :checked、:disabled、:enabled、:in-range、:invalid、:optional、…

CRM系统中的数据分析和报表功能如何帮助企业?

CRM系统中的数据分析和报表功能&#xff1a;企业战略决策的得力助手 在当今竞争激烈的商业环境中&#xff0c;企业要想保持竞争力并实现持续增长&#xff0c;必须依靠精准的数据分析来制定有效的战略决策。而客户关系管理&#xff08;CRM&#xff09;系统的数据分析与报表生成…

Canny 边缘检测

步骤 1.降噪 应用高斯滤波器&#xff0c;以平滑图像&#xff0c;滤除噪声。 边缘检测易受噪声影响&#xff0c;所以使用高斯滤波器平滑图像&#xff0c;降低噪声。 2.梯度 计算图像中每个像素点的梯度大小和方向。 计算大小 Sobel算子是一种常用的边缘检测滤波器&#xff…

深度剖析 C++17 中的 std::byte:解锁字节级编程新境界

文章目录 一、引入背景二、基本定义三、特性详解不可隐式转换为整型显式转换为unsigned char位运算支持字面量支持四、使用场景内存操作数据序列化与反序列化网络通信文件读写操作五、与其他数据类型的交互与字符类型的交互与整数类型的交互与指针类型的交互六、注意事项避免混…

网络原理一>数据链路层协议->以太网协议

目录 以太网协议的结构&#xff1a;类型&#xff1a;ARP请求应答报文&#xff1a;CRC&#xff1a;MTU: 为什么需要mac地址&#xff1a;mac地址和IP地址的区别&#xff1a; 以太网协议的结构&#xff1a; 以太网是数据链路层和物理层的主要协议 源IP&#xff0c;目的IP就不多说…

Day38-【13003】短文,二叉树,完全二叉树,二叉树的顺序存储,和链式存储

文章目录 第二节 二叉树二叉树的定义及重要性质n个结点&#xff0c;能组合成多少个不同的二叉树满二叉树、完全二叉树完全二叉树的性质二叉树的性质二叉树的结点数完全二叉树的高度 二叉树的存储顺序存储方式链式存储方式二叉链表的程序实现二叉链表空指针域计算 第二节 二叉树…

记录 | WPF基础学习Style局部和全局调用

目录 前言一、Style1.1 例子1.2 为样式起名字1.3 BasedOn 继承上一个样式 二、外部StyleStep1 创建资源字典BaseButtonStyle.xamlStep2 在资源字典中写入StyleStep3 App.xaml中写引用路径【全局】Step4 调用三、代码提供四、x:Key和x:Name区别 更新时间 前言 参考文章&#xff…