当前位置: 首页 > news >正文

【JavaEE】JUC 常见的类 -- 多线程篇(8)

JUC 常见的类

  • 1. Callable 接口
  • 2. ReentrantLock
  • 3. 原子类
  • 4. 线程池
  • 5. 信号量 Semaphore
  • 6. CountDownLatch

1. Callable 接口

  • Callable Interface 也是一种创建线程的方式
    • Runnable 能表示一个任务 (run方法) – 返回 void
    • Callable 也能表示一个任务(call方法) 返回一个具体的的值, 类型可以通过泛型参数来指定(Object)

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

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

可以看到, 上述代码需要一个辅助类 Result, 还需要使用一系列的加锁和 wait notify 操作, 代码复杂, 容易出错.


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

  • 创建一个匿名内部类, 实现 Callable 接口. Callable 带有泛型参数. 泛型参数表示返回值的类型.
  • 重写 Callable 的 call 方法, 完成累加的过程. 直接通过返回值返回计算结果.
  • 把 callable 实例使用 FutureTask 包装一下.
  • 创建线程, 线程的构造方法传入 FutureTask . 此时新线程就会执行 FutureTask 内部的 Callable 的call 方法, 完成计算. 计算结果就放到了 FutureTask 对象中.
  • 在主线程中调用 futureTask.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结果.
// 使用 Callable 版本实现 1 累加到 100
public static void main(String[] args) throws ExecutionException, InterruptedException {// 1. 创建实现Callable接口的匿名内部类Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 1; i <= 100; i++) {sum += i;}return sum;}};// 2. 创建futureTask类 -- 要来存储callable的返回值FutureTask<Integer> futureTask = new FutureTask<>(callable);Thread t = new Thread(futureTask);t.start();// 3. 调用futureTask的get方法 -- 该方法是阻塞等待的Integer result = futureTask.get();System.out.println(result);
}
  • 可以看到, 使用 Callable 和 FutureTask 之后, 代码简化了很多, 也不必手动写线程同步代码了.

理解Callable

  • Callable 和 Runnable 相对, 都是描述一个 “任务”. Callable 描述的是带有返回值的任务, Runnable 描述的是不带返回值的任务.
  • Callable 通常需要搭配 FutureTask 来使用. FutureTask 用来保存 Callable 的返回结果. 因为Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定.
  • FutureTask 就可以负责这个等待结果出来的工作.

2. ReentrantLock

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

ReentrantLock 的用法:

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

在这里插入图片描述

ReentrantLock 和 synchronized 的区别:

  • synchronized 是一个关键字, 是 JVM 内部实现的(大概率是基于 C++ 实现). ReentrantLock 是标准库的一个类, 在 JVM 外实现的(基于 Java 实现).
  • synchronized 使用时不需要手动释放锁. ReentrantLock 使用时需要手动释放. 使用起来更灵活, 但是也容易遗漏 unlock.
  • synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的方式等待一段时间就放弃.
  • synchronized 是非公平锁, ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个 true 开启公平锁模式.在这里插入图片描述
  • 更强大的唤醒机制. synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是一个随机等待的线程. ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程.

如何选择使用哪个锁?

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

3. 原子类

使用示例

在这里插入图片描述

原子类的应用场景

  1. 计数需求
    • 播放量, 点赞量…
    • 同一个视频, 有很多人都在同时的播放/点赞
  2. 统计效果
    • 统计出现错误的请求数目 – 使用原子类, 记录出错的请求数目 – 另外写一个监控服务器, 获取线上服务器的这些错误技术, 并且以曲线图的方式绘制到页面上 – 某次发布程序之后, 发现突然这里的错误数大幅度上升, 说明你这个版本代码大概率存在 bug
    • 统计收到请求的总数 (衡量服务器的压力)
    • 统计每个请求的响应事件 -> 平均的响应事件 (衡量服务器的运行效率)
    • 线上服务通过这些统计内容, 进行简单技术 -> 实现监控服务器

4. 线程池

  • 之前的文章里已经讲过了, 请跳转: 线程池

在这里插入图片描述

5. 信号量 Semaphore

信号量的概念

  • 信号量, 用来表示 “可用资源的个数”. 本质上就是一个计数器.

在这里插入图片描述

Semaphore 使用示例

public static void main(String[] args) throws InterruptedException {Semaphore semaphore = new Semaphore(4);int count = 0;// acquire方法 -- P操作 -- 计数器减一semaphore.acquire();System.out.println(count++);// release方法 -- V操作 -- 计数器加一semaphore.release();semaphore.acquire();System.out.println(count++);semaphore.acquire();System.out.println(count++);semaphore.acquire();System.out.println(count++);semaphore.acquire();System.out.println(count++);semaphore.acquire();System.out.println(count++);semaphore.acquire();System.out.println(count++);
}

运行结果如下;
在这里插入图片描述

6. CountDownLatch

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

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

应用场景举例

  • 下载一个大文件, 将大文件分成几个小的文件, 分别让多个线程来执行相应的下载任务, 当所有线程完成任务的时候, 该大文件也就下载完成了;
  • CountDownLatch 就是用来等待所有线程完成任务的
  • 和 join不同的是, join表示执行任务的线程退出了; CountDownLatch 只是等线程完成任务, 线程只要告知CountDownLatch 我完成任务即可, 可以不用被销毁

使用举例

public static void main(String[] args) throws InterruptedException {// 构造方法中, 指定创建几个任务.CountDownLatch countDownLatch = new CountDownLatch(10);for (int i = 0; i < 10; i++) {int id = i + 1;Thread t = new Thread(() -> {System.out.println("线程" + id + "正在工作");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("线程" + id + "完成工作");// 每个任务执行结束这里, 调用一下方法// 把 10 个线程想象成短跑比赛的 10 个运动员. countDown 就是运动员撞线了.countDownLatch.countDown();// 假设线程不退出while (true);});t.start();}// 主线程如何知道上述所有的任务都完成了呢??// 难道要在主线程中调用 10 次 join 嘛?// 万一要是任务结束, 但是线程不需要结束, join 不就也不行了嘛?// 主线程中可以使用 countDownLatch 负责等待任务结束.// a => all 等待所有任务结束. 当调用 countDown 次数 < 初始设置的次数, await 就会阻塞.countDownLatch.await();System.out.println("多个线程的所有任务都执行完毕了");}

相关文章:

【JavaEE】JUC 常见的类 -- 多线程篇(8)

JUC 常见的类 1. Callable 接口2. ReentrantLock3. 原子类4. 线程池5. 信号量 Semaphore6. CountDownLatch 1. Callable 接口 Callable Interface 也是一种创建线程的方式 Runnable 能表示一个任务 (run方法) – 返回 voidCallable 也能表示一个任务(call方法) 返回一个具体的…...

java项目运行时信息获取

大体思路如下&#xff0c;想要获取启动时处理器数量、jvm 相关信息&#xff0c;操作系统信息、运行机器信息 运行机器信息 import org.slf4j.Logger; import org.slf4j.LoggerFactory;import java.lang.invoke.MethodHandles;/*** 机器工具类*/ public abstract class ServerU…...

【LeetCode】71. 简化路径

1 问题 给你一个字符串 path &#xff0c;表示指向某一文件或目录的 Unix 风格 绝对路径 &#xff08;以 / 开头&#xff09;&#xff0c;请你将其转化为更加简洁的规范路径。 在 Unix 风格的文件系统中&#xff0c;一个点&#xff08;.&#xff09;表示当前目录本身&#xf…...

操作系统【OS】进程的控制【进程的创建、终止、阻塞、唤醒】

定义和过程 对应事件 创建 允许一个进程创建另一个进程允许子进程继承父进程所拥有的资源创建进程的过程如下&#xff1a; 申请一个空白的 PCB&#xff0c;并向 PCB 中填写一些控制和管理进程的信息&#xff0c;比如进程的唯一标识等&#xff1b;为该进程分配运行时所必需的…...

写一个简单的解释器(2) 构建标记流

确定标记类型 分为几个大类&#xff1a; 用户符号&#xff08;类型/标识符/数字/字符串…)关键字 (流程控制和定义符)括号 &#xff08;这里暂时认为 [] 属于括号&#xff09;分号 上述四类标记基本囊括了 vc \texttt{vc} vc 中的所有最小单元的类型&#xff0c;但是因为构…...

Leetcode1833. 雪糕的最大数量

Every day a Leetcode 题目来源&#xff1a;1833. 雪糕的最大数量 解法1&#xff1a;贪心 排序 本题唯一的难点在于计数排序。 计数排序详解&#xff1a;C算法之计数排序 为了尽可能多的买到雪糕&#xff0c;我们选择从价格低的雪糕开始买&#xff0c;统计能够买到的雪糕…...

idea 里 没有svn选项的处理办法

总结一下没有svn选项的几种情况&#xff1a; 情况1&#xff1a;IntelliJ IDEA打开带SVN信息的项目不显示SVN信息&#xff0c;项目右键SVN以及图标还有Changes都不显示解决方法 在VCS菜单中有个开关&#xff0c;叫Enabled Version Control Integration&#xff0c;在打开的窗口…...

基于SpringBoot的招生管理系统

基于SpringBoot的招生管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 登录界面 管理员界面 用户界面 摘要 基于SpringBoot的招生管理系统是一款现…...

01、MySQL-------性能优化

目录 一、影响性能的相关因素存储过程&#xff1a; 二、sql优化1>、Mysql系统架构2>、引擎区别&#xff1a; 3>、索引1、什么是索引&#xff1f;联合主键索引理解&#xff1a;索引长度理解&#xff1a;什么是慢查询&#xff1f; 1&#xff09;、索引理解2&#xff09;…...

Flutter - APP跳转高德、百度、腾讯、谷歌地图

demo 地址: https://github.com/iotjin/jh_flutter_demo 代码不定时更新&#xff0c;请前往github查看最新代码 这里介绍的是不需要自己开发地图&#xff0c;直接通过给定的经纬度&#xff0c;跳转到三方地图APP调用导航的方式 一种是写的工具类&#xff0c;一种是通过调用三方…...

Flyway Desktop updated

Flyway Desktop updated 为比较工件序列化和反序列化添加了额外的调试日志记录。 Flyway Desktop现在将记住以前用于创建项目和匹配克隆的位置。 新的脱机许可工作流现在已在Microsoft Windows上启用。 现在&#xff0c;在配置目标数据库列表时&#xff0c;环境ID是可见的。 现…...

阿里云短信服务设置操作项目

在这里插入图片描述...

学习笔记|串口通信实战|简易串口控制器|sprintf函数|STC32G单片机视频开发教程(冲哥)|第二十一集(下):串口与PC通信

目录 3.串口通信实战实操简易的工作原理Tips:sprintf函数简介 总结课后练习 3.串口通信实战 做一个简易串口控制器。发送对应指令&#xff0c;让板子做相应的事情&#xff0c;或者传输数据&#xff08;文本模式下发送&#xff0c;不要选择HEX&#xff09;。 1.串口发送字符Ax\…...

卷积神经网络CNN学习笔记-卷积计算Conv2D函数的理解

目录 1.全连接层存在的问题2.卷积运算3.填充(padding)3.1填充(padding)的意义 4.步幅(stride)5.三维数据的卷积运算6.结合方块思考7.批处理8.Conv2D函数解析9.conv2d代码9.1 stride19.2 stride2 参考文章 1.全连接层存在的问题 在全连接层中&#xff0c;相邻层的神经元全部连接…...

收藏,安装报错信息汇总,MacOS上安装Adobe等软件/插件报错问题解决合集

打开允许“允许任何来源” 如何打开允许任何来源&#xff1f;在 Finder 菜单栏选择 【前往】 – 【实用工具 】&#xff0c;找到【终端】程序&#xff0c;双击打开&#xff0c;在终端窗口中输入&#xff1a;sudo spctl --master-disable 输入代码后&#xff0c;按【return 回车…...

Qt 报错:munmap_chunk(): invalid pointer解决方案

问题 在用Qt写程序的时候。报了munmap_chunk(): invalid pointer这个错误消息。 造成原因 在 Qt 程序中&#xff0c;这种错误可能出现在多种情况下&#xff0c;以下几点是容易造成此问题的原因&#xff1a; 函数未实现返回值&#xff1a;函数有返回值&#xff0c;但函数体中…...

【Java题】实现继承和多态的例子

一&#xff1a;题目 1.员工类Employee&#xff1a; &#xff08;1&#xff09;私有成员变量&#xff1a;姓名&#xff0c;年龄&#xff0c;工资 &#xff08;2&#xff09;提供无参&#xff0c;有参构造 &#xff08;3&#xff09;成员方法&#xff1a;work()方法——员工工作 …...

‘conda‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。

原因&#xff1a;环境变量没有正确添加解决&#xff1a;我的电脑—右键属性—高级系统设置—环境变量—系统变量—Path—双击进入—新建&#xff0c;去安装Anaconda的目录下&#xff0c;找到Library中的bin&#xff0c;将此时的路径粘贴到此处...

C1N短网址 - 是如何做到行业领先的

今天从技术角度来聊下短网址的一些事情&#xff0c;市面上的短网址发展基本上经历了几个阶段。 短网址发展的几个阶段&#xff1a; 第一阶段&#xff1a;网址缩短&#xff0c;很纯粹的功能&#xff0c;各个大小公司都在做&#xff0c;门槛很低。典型代表&#xff1a;百度短网…...

【UE5】引入C++插件Plugins不在UE里出现

原因 未编译过C 原项目为蓝图项目&#xff0c;或者虽然为C项目&#xff0c;但并为编译过C. 解决 创建一个C脚本&#xff0c;让编辑器重启重新编译一遍。 如还不行&#xff0c;则打开Plugins插件面板&#xff0c;创建一个空的新的插件&#xff0c;再让引擎自动重启重新编译…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)

0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述&#xff0c;后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作&#xff0c;其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...