Java 多线程(九)—— JUC 常见组件 与 线程安全的集合类

Callable 与 FutureTask

Callable 接口和 Runnable 接口是并列关系,都是用来给线程提供任务的,只不过 Callable 接口的任务可以带有返回值。

在这里插入图片描述
但是 Callable 接口创建的任务不能直接传入 Thread 里面,这也是为了 解耦合,我们可以使用 FutureTask 这个玩意来接收一下 Callable 接口定义的任务,然后再通过 FutureTask 传给 Thread里面。

    public static void main(String[] args) {
        Callable<Integer> c = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i < 5000; i++) {
                    sum++;
                }
                return sum;
            }
        };

        FutureTask<Integer> futureTask = new FutureTask<>(c);

        Thread t = new Thread(futureTask);
        t.start();
    }

ReentrantLock 与 原子类

这些已经在上一篇文章中提到,不了解的可以阅读此文:Java 多线程(八)—— 锁策略,synchronized 的优化,JVM 与编译器的锁优化,ReentrantLock,CAS

Semaphore 信号量

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

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

在操作系统中,每一次的 P 操作就会让信号量 - 1,每一次的 V 操作就会让信号量 + 1

P 操作表示向操作系统申请资源, V 操作表示释放资源

在 Java 中,我们可以实例化 Semophore ,使用 acquire 方法表示申请资源(P操作),release 方法表示释放资源(V操作)

代码演示:

    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(2);

        semaphore.acquire();
        System.out.println("进行了一次P操作");
        semaphore.acquire();
        System.out.println("进行了一次P操作");
        semaphore.release();
        System.out.println("进行了一次V操作");
        semaphore.release();
        System.out.println("进行了一次V操作");
    }

在这里插入图片描述

这里要注意:如果资源不够的话,那就不能进行资源分配,该申请资源的线程会阻塞等待(死等 waiting)状态,直到有资源分配为止。

所以我们可以利用 Semaphore 这一个特性来制作一个类似锁的功能,我们给 Semaphore 传入一个信号量。

    private static int sum = 0;
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(1);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                try {
                    semaphore.acquire();
                    sum++;
                    semaphore.release();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                try {
                    semaphore.acquire();
                    sum++;
                    semaphore.release();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("sum = " + sum);
    }

在这里插入图片描述

CountDownLatch

我们在使用多线程经常把一个大的任务拆分成很多个子任务,当这些线程去执行这些任务的时候,我们如何判断这些子任务都完成了呢?整个任务都完成了呢?

这时候我们可以使用 CountDownLatch 来进行统计任务执行的次数。

构造方法:
在这里插入图片描述
count 参数表示任务总数

CountDownLatch 提供 countDown() 方法,在每次执行完一个子任务之后,我们就调用一次这个方法,让计数器减 1.
同时也提供了 await 方法:阻塞等待所有的子任务执行完毕,也就是计数器为 0,开始执行该线程的任务。

代码演示:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);

        ExecutorService executorService = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 10; i++) {
            int id = i;
            executorService.submit(() -> {
                System.out.println("子任务开始执行:" + id);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(id + "子任务执行完毕");
                countDownLatch.countDown();
            });
        }

        countDownLatch.await();
        System.out.println("等待所有任务执行完毕......");
        executorService.shutdown();
    }
}

在这里插入图片描述

线程安全的集合类

多线程环境使用 ArrayList

推荐自行加锁

在需要加锁的地方我们手动去加锁,自己打包好原子操作。

Collections.synchronizedList()

synchronizedList 是标准库提供的⼀个基于 synchronized 进行线程同步的 List.synchronizedList 的关键操作上都带有 synchronized

代码演示:Collections.synchronizedList(new ArrayList<Integer>(10));

CopyOnWriteArrayList

CopyOnWrite容器即写时复制的容器

当我们要去往一个容器进行写操作的时候,不是直接在原来的地方进行写操作,而是先将容器拷贝一份,在拷贝的哪个容器上进行写操作,等到写操作结束之后,我们再将原来容器的引用指向为拷贝的容器。

代码演示:CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();

这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

优点:在读多写少的场景下,性能很高,不需要加锁竞争。

缺点:1.占用内存较多。
2.新写的数据不能被第一时间读取到。

多线程环境使用队列

  1. ArrayBlockingQueue
    基于数组实现的阻塞队列

  2. LinkedBlockingQueue
    基于链表实现的阻塞队列

  3. PriorityBlockingQueue
    基于堆实现的带优先级的阻塞队列

  4. TransferQueue
    最多只包含⼀个元素的阻塞队列

多线程环境使用哈希表

HashMap 本身不是线程安全的

Hashtable

只是简单的把关键方法加上了 synchronized 关键字

在这里插入图片描述

这个实现不是很好,当多个线程进行对这个哈希表进行操作的时候,很容易就会发生锁冲突。
同时,size 属性也是通过 synchronized 来控制同步,一旦触发扩容,就由该线程完成整个扩容过程。这个过程会涉及到大量的元素拷贝,效率会非常低.

所以我们不会去使用这个玩意,我们会选择下面的 ConcurrentHashMap

ConcurrentHashMap

读操作没有加锁(但是使用了volatile保证从内存读取结果),只对写操作进行加锁。加锁的方式仍然是用synchronized,但是不是锁整个对象,而是 “锁桶” (用每个链表的头结点作为锁对象),大大降低了锁冲突的概率.

在这里插入图片描述

充分利用 CAS 特性,比如 size 属性通过 CAS 来更新。避免出现重量级锁的情况。

优化了扩容方式:化整为零
发现需要扩容的线程,只需要创建一个新的数组,同时只搬几个元素过去。
扩容期间,新老数组同时存在。
后续每个来操作 ConcurrentHashMap 的线程,都会参与搬家的过程。每个操作负责搬运一小部分元素。 搬完最后⼀个元素再把老数组删掉。
这个期间,插入只往新数组加。
这个期间,查找需要同时查新数组和老数组。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pdf合并,这4款好用软件分分钟解决问题!

PDF作为一种跨平台、不易被篡改的文档格式&#xff0c;广泛应用于工作、学习和日常生活中。然而&#xff0c;当面对多个PDF文件需要合并成一个时&#xff0c;繁琐的手动操作往往让人头疼不已。别担心&#xff0c;今天就给大家安利4款超实用的PDF合并软件&#xff0c;它们不仅操…

c++二级指针

如果要通过函数改变一个指针的值&#xff0c;要往函数中传入指针的指针 如果要通过函数改变一个变量的值&#xff0c;那就要往函数中传入这个变量的地址 改变a的值和b的值 #include <iostream>using namespace std;void swap(int* a, int* b) {int temp *a;*a *b;*b …

pyvideotrans 最佳AI翻译软件

文章目录 体验视频翻译配音工具主要用途和功能预打包版本(仅win10/win11可用&#xff0c;MacOS/Linux系统使用源码部署)MacOS源码部署Linux 源码部署Window10/11 源码部署源码部署问题说明使用教程和文档语音识别模型:视频教程(第三方)软件预览截图相关联项目致谢 体验 不错&a…

根据Redis漏洞通知的整改修复过程

一、收到通知&#xff1a; 二、查看本校“宝山商城&#xff08;教学&#xff09;”已安装的Redis版本号 对照影响范围的版本号&#xff0c;在其内&#xff0c;所以需要升级Redis版本。 三、升级centos中的Redis版本 在Cent0S系统中&#xff0c;如果我们需要升级Redis版本&…

C++,STL 048(24.10.25)

内容 set容器对内置数据类型、自定义数据类型指定排序规则。 运行代码 &#xff08;1&#xff09;内置数据类型 #include <iostream> #include <set>using namespace std;// set容器默认排序规则为升序&#xff08;从小到大&#xff09;&#xff0c;可以通过仿函…

若依框架篇-若依集成 X-File-Storage 框架(实现图片上传阿里云 OSS 服务器)、EasyExcel 框架(实现 Excel 数据批量导入功能)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 实现使用 Excel 文件批量导入 1.1 导入功能的前端具体实现 1.2 导入功能的后端具体实现 1.3 使用 EasyExcel 框架实现 Excel 读、写功能 1.4 将 Easy Excel 集成到…

基于SSM+微信小程序考试的管理系统(考试1)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM微信小程序考试的管理系统实现了管理员及用户。 1、管理员功能有个人中心&#xff0c;用户管理&#xff0c;考试资料管理&#xff0c;用户交流管理&#xff0c;试卷管理&#xff…

新能源汽车充电设施在储充电站的应用

0引言 全球能源和环境问题促使新能源汽车受到关注&#xff0c;但其推广受充电设施和能源供应限制。光伏站、储能站和电动汽车充放电站作为可再生能源利用和储存方式&#xff0c;具有巨大潜力。本研究旨在探索新能源汽车充电设施与这些站点的融合模式&#xff0c;以支持新能源汽…

【ROS GitHub使用】

提示&#xff1a;环境配置为Ubuntu20.04&ROS Noetic 文章目录 前言一、创建工作空间目录二、尝试从GitHub上下载一个源码包&#xff0c;对它进行编译&#xff0c;运行这个源码包1.打开script文件夹&#xff0c;右键文件夹空白区域&#xff0c;选择在中端中打开&#xff1b;…

OceanBase 安全体系解析之身份鉴别

本文作者&#xff1a;金长龙爱可生测试工程师&#xff0c;负责 DMP 产品的测试工作。 本文以MySQL为参照&#xff0c;详细阐述了OceanBase 在MySQL模式下的安全体系中&#xff0c;身份鉴别的能力&#xff0c;涵盖了身份鉴别机制、用户名的构成规则、密码的复杂度&#xff0c;以…

ctfshow(66->70)--RCE/命令执行漏洞--禁用命令执行函数

Web66 源代码&#xff1a; if(isset($_POST[c])){$c $_POST[c];eval($c); }else{highlight_file(__FILE__); }代码审计&#xff1a; POST传参c&#xff0c;eval进行代码执行。 思路&#xff1a; 由于题目过滤了命令执行函数&#xff0c;所以使用其他方法进行RCE。 先使用c…

自定义类型1:结构体的深入学习

文章目录 前言一、结构体类型的声明1、结构体回顾1.1、结构体声明1.2、结构体变量的创建和初始化 2、结构的特殊声明3、结构体的自引用 二、结构体的内存对齐1&#xff0c;什么叫偏移量2、对齐规则3、为什么存在内存对齐4、修改默认对齐数 三、结构体传参四、结构体实现位段1、…

通过异地组网工具+RustDesk实现虚拟局域网使用远程桌面RDP

通过异地组网工具RustDesk实现虚拟局域网使用远程桌面RDP 预期效果 常见的远程桌面工具就不多说&#xff0c;麻烦而且不好用 QQ 使用普及率高 卡顿、延迟高 TeamViewer 功能强大、兼容性好 官方查询商业用途频繁 向日葵 安全性高、支持多种设备 强制登录、免费用户限速、限…

详解varint,zigzag编码, 以及在Go标准库中的实现

文章目录 为啥需要varint编码为啥需要zigzag编码varint编码解码 zigzag编码解码 局限性 为啥需要varint编码 当我们用定长数字类型int32来表示整数时&#xff0c;为了传输一个整数1&#xff0c;我们需要传输00000000 00000000 00000000 00000001 32 个 bits&#xff0c;而有价…

Oracle CONNECT BY、PRIOR和START WITH关键字详解

Oracle CONNECT BY、PRIOR和START WITH关键字详解 1. 基本概念2. 数据示例3. SQL示例3.1. 查询所有员工及其上级3.2. 显示层次结构3.3. 查询特定员工的子级 4. 结论 在Oracle数据库中&#xff0c;CONNECT BY、PRIOR和START WITH关键字主要用于处理层次结构数据&#xff0c;例如…

PostgreSQL的学习心得和知识总结(一百五十六)|auto_explain — log execution plans of slow queries

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

基于 Python 的机器学习模型部署到 Flask Web 应用:从训练到部署的完整指南

目录 引言 技术栈 步骤一&#xff1a;数据预处理 步骤二&#xff1a;训练机器学习模型 步骤三&#xff1a;创建 Flask Web 应用 步骤四&#xff1a;测试 Web 应用 步骤五&#xff1a;模型的保存与加载 保存模型 加载模型并在 Flask 中使用 步骤六&#xff1a;Web 应用…

在xml 中 不等式 做转义处理的问题

对于这种要做转义处理&#xff0c;<![CDATA[ < ]]>

图文详解ChatGPT-o1完成论文写作的全流程

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 本月中旬OpenAI发布了OpenAI o1系列新的AI模型。 据OpenAI介绍&#xff0c;这些模型旨在花更多时间思考后再做出反应&#xff0c;就像人一样。通过训练&#xff0c;它们学会改进思维过…

如何制定有效的学习计划

文章目录 第一章&#xff1a;目标设定1.1 目标的重要性1.2 SMART原则1.3 目标设定公式 第二章&#xff1a;时间管理2.1 时间的重要性2.2 制定时间表2.3 时间管理公式2.4 番茄工作法2.5 时间分配公式 第三章&#xff1a;学习策略3.1 学习方法3.2 学习材料的选择3.3 学习效果公式…