【Java EE】JUC(java.util.concurrent) 的常见类

目录

  • 🌴Callable 接口
  • 🎍ReentrantLock
  • 🍀原子类
  • 🌳线程池
  • 🌲信号量 Semaphore
  • ☘️CountDownLatch、
  • ⭕相关面试题

🌴Callable 接口

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

**代码示例: **

创建线程计算 1 + 2 + 3 + … + 1000, 不使⽤ Callable 版本。

• 创建⼀个类 Result , 包含⼀个 sum 表⽰最终结果, lock 表⽰线程同步使⽤的锁对象.
• main ⽅法中先创建 Result 实例, 然后创建⼀个线程 t. 在线程内部计算 1 + 2 + 3 + … + 1000.
• 主线程同时使⽤ wait 等待线程 t 计算结束. (注意, 如果执⾏到 wait 之前, 线程 t 已经计算完了, 就不
必等待了).
• 当线程 t 计算完毕后, 通过 notify 唤醒主线程, 主线程再打印结果.

public class Demo {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 操作, 代码复杂,
容易出错.

使⽤ Callable 版本代码示例

• 创建⼀个匿名内部类, 实现 Callable 接⼝. Callable 带有泛型参数. 泛型参数表⽰返回值的类型.
• 重写 Callable 的 call ⽅法, 完成累加的过程. 直接通过返回值返回计算结果.
• 把 callable 实例使⽤ FutureTask 包装⼀下.
• 创建线程, 线程的构造⽅法传⼊ FutureTask . 此时新线程就会执⾏ FutureTask 内部的 Callable 的
call ⽅法, 完成计算. 计算结果就放到了 FutureTask 对象中.
• 在主线程中调⽤ futureTask.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的
结果.

public class CallableTest {public static void main(String[] args) throws ExecutionException, InterruptedException {Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int sum = 0;for(int i = 1; i<=1000; i++){sum+=i;}return sum;}};FutureTask<Integer> futuretask = new FutureTask<>(callable);Thread t = new Thread(futuretask);t.start();int result = futuretask.get();System.out.println(result);}
}

可以看到, 使⽤ Callable 和 FutureTask 之后, 代码简化了很多, 也不必⼿动写线程同步代码了.

理解 Callable

Callable 和 Runnable 相对, 都是描述⼀个 “任务”. Callable 描述的是带有返回值的任务, Runnable
描述的是不带返回值的任务
.

Callable 通常需要搭配 FutureTask 来使⽤. FutureTask ⽤来保存 Callable 的返回结果. 因为
Callable 往往是在另⼀个线程中执⾏的, 啥时候执⾏完并不确定.

FutureTask 就可以负责这个等待结果出来的⼯作.

理解 FutureTask

想象去吃⿇辣烫. 当餐点好后, 后厨就开始做了. 同时前台会给你⼀张 “⼩票” . 这个⼩票就是
FutureTask. 后⾯我们可以随时凭这张⼩票去查看⾃⼰的这份⿇辣烫做出来了没.

🎍ReentrantLock

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

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

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

ReentrantLock lock = new ReentrantLock(); 
-----------------------------------------
lock.lock(); 
try { // working 
} finally { lock.unlock() 
}

ReentrantLock 和 synchronized 的区别:

• synchronized 是⼀个关键字, 是 JVM 内部实现的(⼤概率是基于 C++ 实现). ReentrantLock 是标准
库的⼀个类, 在 JVM 外实现的(基于 Java 实现).
• synchronized 使⽤时不需要⼿动释放锁. ReentrantLock 使⽤时需要⼿动释放. 使⽤起来更灵活, 但
是也容易遗漏 unlock.
• synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的⽅式等待⼀段时间就放
弃.
• synchronized 是⾮公平锁, ReentrantLock 默认是⾮公平锁. 可以通过构造⽅法传⼊⼀个 true 开启
公平锁模式.

// ReentrantLock 的构造⽅法
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}

• 更强⼤的唤醒机制. synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是⼀个
随机等待的线程. ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定
的线程.

如何选择使⽤哪个锁?
• 锁竞争不激烈的时候, 使⽤ synchronized, 效率更⾼, ⾃动释放更⽅便.
• 锁竞争激烈的时候, 使⽤ ReentrantLock, 搭配 trylock 更灵活控制加锁的⾏为, ⽽不是死等.
• 如果需要使⽤公平锁, 使⽤ ReentrantLock

🍀原子类

原⼦类内部⽤的是 CAS 实现,所以性能要⽐加锁实现 i++ ⾼很多。原⼦类有以下⼏个
• AtomicBoolean
• AtomicInteger
• AtomicIntegerArray
• AtomicLong
• AtomicReference
• AtomicStampedReference

以 AtomicInteger 举例,常⻅⽅法有

addAndGet(int delta); i += delta;
decrementAndGet(); --i;
getAndDecrement(); i--;
incrementAndGet(); ++i;
getAndIncrement(); i++;

使用示例:

public class AtomicTest {static AtomicInteger count = new AtomicInteger();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 5000; i++) {count.getAndIncrement();}});Thread t2 = new Thread(()->{for (int i = 0; i < 5000; i++) {count.getAndIncrement();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count.get());}
}

🌳线程池

虽然创建销毁线程⽐创建销毁进程更轻量, 但是在频繁创建销毁线程的时候还是会⽐较低效.

线程池就是为了解决这个问题. 如果某个线程不再使⽤了, 并不是真正把线程释放, ⽽是放到⼀个 “池
⼦” 中, 下次如果需要⽤到线程就直接从池⼦中取, 不必通过系统来创建了.

ExecutorService 和 Executors

关于线程池这部分大家可以看博主之前的线程池详解

🌲信号量 Semaphore

信号量, ⽤来表⽰ “可⽤资源的个数”. 本质上就是⼀个计数器

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

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

代码⽰例
• 创建 Semaphore ⽰例, 初始化为 4, 表⽰有 4 个可⽤资源.
• acquire ⽅法表⽰申请资源(P操作), release ⽅法表⽰释放资源(V操作)
• 创建 20 个线程, 每个线程都尝试申请资源, sleep 1秒之后, 释放资源. 观察程序的执⾏效果.

public class Test {public static void main(String[] args) {Semaphore semaphore = new Semaphore(4);Runnable runnable = new Runnable() {@Overridepublic void run() {try {System.out.println("申请资源");semaphore.acquire();System.out.println("我获取到资源了");Thread.sleep(1000);System.out.println("我释放资源了");semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}}};for (int i = 0; i < 20; i++) {Thread t = new Thread(runnable);t.start();}}

☘️CountDownLatch、

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

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

• 构造 CountDownLatch 实例, 初始化 10 表⽰有 10 个任务需要完成.
• 每个任务执⾏完毕, 都调⽤ latch.countDown() . 在 CountDownLatch 内部的计数器同时⾃
减.
• 主线程中使⽤ latch.await(); 阻塞等待所有任务执⾏完毕. 相当于计数器为 0 了.

public class Demo {public static void main(String[] args) throws Exception {CountDownLatch latch = new CountDownLatch(10);Runnable r = new Runable() {@Overridepublic void run() {try {Thread.sleep(Math.random() * 10000);latch.countDown();} catch (Exception e) {e.printStackTrace();}}};for (int i = 0; i < 10; i++) {new Thread(r).start();}// 必须等到 10 ⼈全部回来latch.await();System.out.println("⽐赛结束");}
}

⭕相关面试题

  1. 线程同步的⽅式有哪些?

synchronized, ReentrantLock, Semaphore 等都可以⽤于线程同步

  1. 为什么有了 synchronized 还需要 juc 下的 lock?

以 juc 的 ReentrantLock 为例,

• synchronized 使⽤时不需要⼿动释放锁. ReentrantLock 使⽤时需要⼿动释放. 使⽤起来更灵活,
• synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的⽅式等待⼀段时间就放
弃.
• synchronized 是⾮公平锁, ReentrantLock 默认是⾮公平锁. 可以通过构造⽅法传⼊⼀个 true 开启
公平锁模式.
• synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是⼀个随机等待的线程.
ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程.

  1. AtomicInteger 的实现原理是什么?

基于 CAS 机制. 伪代码如下:

class AtomicInteger {private int value;public int getAndIncrement() {int oldValue = value;while ( CAS(value, oldValue, oldValue+1) != true) {oldValue = value;}return oldValue;}
}

执⾏过程参考 “CAS详解与应用” 博客.

  1. 信号量听说过么?之前都⽤在过哪些场景下?

信号量, ⽤来表⽰ “可⽤资源的个数”. 本质上就是⼀个计数器.
比特就业课
使⽤信号量可以实现 “共享锁”, ⽐如某个资源允许 3 个线程同时使⽤, 那么就可以使⽤ P 操作作为加
锁, V 操作作为解锁, 前三个线程的 P 操作都能顺利返回, 后续线程再进⾏ P 操作就会阻塞等待, 直到前
⾯的线程执⾏了 V 操作.

  1. 解释⼀下 ThreadPoolExecutor 构造⽅法的参数的含义

参考博主的 线程池详解 博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/716825.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是灰色预测

灰色预测是一种基于灰色系统理论的预测方法&#xff0c;用于处理数据不完全、信息不充分或未知的情况下的预测问题。它适用于样本数据较少、无法建立精确的数学模型的情况。 灰色预测的基本思想是利用已知数据的特点和规律来推断未知数据的发展趋势。它的核心是灰色关联度的概念…

(学习日记)2024.03.01:UCOSIII第三节 + 函数指针 (持续更新文件结构)

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

Kubernetes: 本地部署dashboard

本篇文章主要是介绍如何在本地部署kubernetes dashboard, 部署环境是mac m2 下载dashboard.yaml 官网release地址: kubernetes/dashboard/releases 本篇文章下载的是kubernetes-dashboard-v2.7.0的版本&#xff0c;通过wget命令下载到本地: wget https://raw.githubusercont…

【Python】进阶学习:pandas--isin()用法详解

【Python】进阶学习&#xff1a;pandas–isin()用法详解 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望得到您的订阅…

【NDK系列】Android tombstone文件分析

文件位置 data/tombstone/tombstone_xx.txt 获取tombstone文件命令&#xff1a; adb shell cp /data/tombstones ./tombstones 触发时机 NDK程序在发生崩溃时&#xff0c;它会在路径/data/tombstones/下产生导致程序crash的文件tombstone_xx&#xff0c;记录了死亡了进程的…

单细胞Seurat - 细胞聚类(3)

本系列持续更新Seurat单细胞分析教程&#xff0c;欢迎关注&#xff01; 维度确定 为了克服 scRNA-seq 数据的任何单个特征中广泛的技术噪音&#xff0c;Seurat 根据 PCA 分数对细胞进行聚类&#xff0c;每个 PC 本质上代表一个“元特征”&#xff0c;它结合了相关特征集的信息。…

深入测探:用Python玩转分支结构与循环操作——技巧、场景及面试宝典

在编程的世界里&#xff0c;分支结构和循环操作是构建算法逻辑的基础砖石。它们如同编程的“盐”&#xff0c;赋予代码生命&#xff0c;让静态的数据跳跃起来。本文将带你深入探索Python中的分支结构和循环操作&#xff0c;通过精心挑选的示例和练习题&#xff0c;不仅帮助你掌…

mysql5*-mysql8 区别

1.Mysql5.7-Mysql8.0 sysbench https://github.com/geekgogie/mysql57_vs_8-benchmark_scripts 1.读、写、删除更新 速度 512 个线程以后才会出现如下的。 2.删除速度 2.事务处理性能 3.CPU利用率 mysql8 利用率高。 4.排序 5.7 只能ASC&#xff0c;不能降序 数据越来越大

牢记于心单独说出来的知识点(后续会加)

第一个 非十进制&#xff08;八进制&#xff0c;十六进制&#xff09;写在文件中它本身就是补码&#xff0c;计算机是不用进行内存转换&#xff0c;它直接存入内存。&#xff08;因为十六进制本身是补码&#xff0c;所以计算机里面我们看到的都是十六进制去存储&#xff09; …

Qt 简约美观的加载动画 文本风格 第八季

今天和大家分享一个文本风格的加载动画, 有两类,其中一个可以设置文本内容和文本颜色,演示了两份. 共三个动画, 效果如下: 一共三个文件,可以直接编译 , 如果对您有所帮助的话 , 不要忘了点赞呢. //main.cpp #include "LoadingAnimWidget.h" #include <QApplic…

MySQL:开始深入其数据(一)DML

在上一章初识MySQL了解了如何定义数据库和数据表&#xff08;DDL&#xff09;&#xff0c;接下来我们开始开始深入其数据,对其数据进行访问&#xff08;DAL&#xff09;、查询DQL&#xff08;&#xff09;和操作(DML)等。 通过DML语句操作管理数据库数据 DML (数据操作语言) …

一文搞定 FastAPI 路径参数

路径参数定义 路径操作装饰器中对应的值就是路径参数,比如: from fastapi import FastAPI app = FastAPI()@app.get("/hello/{name}") def say_hello(name: str):return {

突破编程_C++_STL教程( list 的基础知识)

1 std::list 概述 std::list 是 C 标准库中的一个双向链表容器。它支持在容器的任何位置进行常数时间的插入和删除操作&#xff0c;但不支持快速随机访问。与 std::vector 或 std::deque 这样的连续存储容器相比&#xff0c;std::list 在插入和删除元素时不需要移动其他元素&a…

计算机网络之传输层 + 应用层

.1 UDP与TCP IP中的检验和只检验IP数据报的首部, 但UDP的检验和检验 伪首部 首部 数据TCP的交互单位是数据块, 但仍说TCP是面向字节流的, 因为TCP仅把应用层传下来的数据看成无结构的字节流, 根据当时的网络环境组装成大小不一的报文段.10秒内有1秒用于发送端发送数据, 信道…

【Python】进阶学习:pandas--groupby()用法详解

&#x1f4ca;【Python】进阶学习&#xff1a;pandas–groupby()用法详解 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448;…

Python算法100例-3.5 亲密数

1.问题描述2.问题分析3.算法设计4.确定程序框架5.完整的程序6.问题拓展 1&#xff0e;问题描述 如果整数A的全部因子&#xff08;包括1&#xff0c;不包括A本身&#xff09;之和等于B&#xff0c;且整数B的全部因子&#xff08;包括1&#xff0c;不包括B本身&#xff09;之和…

中国电子学会2020年6月份青少年软件编程Sc ratch图形化等级考试试卷四级真题。

第 1 题 【 单选题 】 1.执行下面程序&#xff0c;输入4和7后&#xff0c;角色说出的内容是&#xff1f; A&#xff1a;4&#xff0c;7 B&#xff1a;7&#xff0c;7 C&#xff1a;7&#xff0c;4 D&#xff1a;4&#xff0c;4 2.执行下面程序&#xff0c;输出是&#xff…

Oracle自带的网络工具(计算传输redo需要的带宽,使用STATSPACK,计算redo压缩率,db_ultra_safe)

--根据primary database redo产生的速率,计算传输redo需要的带宽. 除去tcp/ip网络其余30%的开销,计算需要的带宽公式: 需求带宽((每秒产生redo的速率峰值/0.75)*8)/1,000,000带宽(Mbps) --可以通过去多次业务高峰期的Statspack/AWR获取每秒产生redo的速率峰值,也可以通过查询视…

post请求体内容无法重复获取

post请求体内容无法重复获取 为什么会无法重复读取呢&#xff1f; 以tomcat为例&#xff0c;在进行请求体读取时实际底层调用的是org.apache.catalina.connector.Request的getInputStream()方法&#xff0c;而该方法返回的是CoyoteInputStream输入流 public ServletInputStream…

CVE-2016-5195 复现记录

文章目录 poc前置知识页表与缺页异常/proc/self/mem的写入流程madvise 漏洞点修复 Dirty COW脏牛漏洞是一个非常有名的Linux竞争条件漏洞&#xff0c;虽然早在2016年就已经被修复&#xff0c;但它依然影响着众多古老版本的Linux发行版&#xff0c;如果需要了解Linux的COW&#…