初始JavaEE篇——多线程(8):JUC的组件

找往期文章包括但不限于本期文章中不懂的知识点:

个人主页:我要学编程程(ಥ_ಥ)-CSDN博客

所属专栏:JavaEE

目录

Callable接口

ReentrantLock

synchronized 与 ReentrantLock的区别 

信号量(Semaphore)

CountDownLatch

多线程下使用ArrayList、哈希表


JUC 是值 java.util.current 包,现在我们要学习这些包中的一些常用的类。

Callable接口

Callable 接口与Runnable接口一样,都是用来包装任务的,只不过Callable接口有泛型参数且其方法有返回值,我们下面就来演示Callable接口的使用。

代码演示:

public class Test {private static int count = 0;public static void main(String[] args) throws InterruptedException {Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception { // 这里面是任务与run方法类似for (int i = 0; i < 10000; i++) {count++;}return count;}};// 将任务进一步分装起来FutureTask<Integer> task = new FutureTask<>(callable);// 再将任务给到ThreadThread t = new Thread(task);t.start();t.join();System.out.println("count: "+ count);}
}

下面是lambda表达式的写法:

public class Test {private static int count = 0;public static void main(String[] args) throws InterruptedException {// 使用lambda表达式代替匿名内部类FutureTask<Integer> task = new FutureTask<>(()->{for (int i = 0; i < 10000; i++) {count++;}return count;});Thread t = new Thread(task);t.start();t.join();System.out.println("count: "+count);}
}

上面是我们手动使 main线程阻塞等待 t线程执行完毕,我们还可以使用 FutureTask 中的get方法,从而去被动阻塞 main线程。

public class Test {private static Object locker = new Object();public static void main(String[] args) throws InterruptedException, ExecutionException {FutureTask<Integer> task = new FutureTask<>(()->{int sum = 0;for (int i = 1; i <= 1000; i++) {sum += i;}return sum;});Thread t = new Thread(task);t.start();// 在调用get方法时,得到的是call方法的返回值,即主线程会阻塞等待t线程执行完call方法System.out.println(task.get());}
}

总结:Callable 接口也是函数式接口,通过 call 方法来完成任务,最终的任务需要被 FutureTask进一步分装,从而给到Thread。

ReentrantLock

ReentrantLock是可重入互斥锁,和 synchronized 定位类似,都是用来实现互斥效果,保证线程安全。但是ReentrantLock 是Java标准库提供的一个类,而不是关键字。

ReentrantLock 有三种方法:

1、lock():加锁,如果获取不到锁就会一直等待,也就是死等。

2、tryLock(超时时间,时间级别):加锁,如果在超时时间之内没有获取到锁,也是一直等待,但如果超出了超时时间的话,就会放弃等待。

3、unlock():解锁。

代码演示:

1、lock—unlock方法的使用:

public class Test {private static int count = 0;public static void main(String[] args) throws InterruptedException {ReentrantLock reentrantLock = new ReentrantLock();Thread t1 = new Thread(()->{for (int i = 0; i < 100000; i++) {reentrantLock.lock();count++;reentrantLock.unlock();}});Thread t2 = new Thread(()->{for (int i = 0; i < 100000; i++) {reentrantLock.lock();count++;reentrantLock.unlock();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count: "+count);}
}

 2、tryLock方法的使用:

public class Test {public static void main(String[] args) throws InterruptedException {ReentrantLock reentranLock = new ReentrantLock();Thread t = new Thread(()->{try {reentranLock.lock();System.out.println("t线程开始");Thread.sleep(3000);System.out.println("t线程结束");reentranLock.unlock();} catch (InterruptedException e) {throw new RuntimeException(e);}});t.start();Thread.sleep(1000); // 确保t线程先加锁成功// 等待1s之后,就会不等了boolean state = reentranLock.tryLock(1000, TimeUnit.MILLISECONDS);if (state) {System.out.println("main线程成功获取锁");reentranLock.unlock();} else {System.out.println("main线程获取锁失败");}System.out.println("main线程结束");}
}

运行结果:

tryLock还有一个重载的方法,是不带有任何参数的,其含义是:尝试去获取锁时,如果锁处于空闲状态就返回true,否则就返回false。这个是不会去等待的。 

synchronized 与 ReentrantLock的区别 

1、synchronized 是关键字,其底层的实现是JVM内部通过C++实现的,而 ReentrantLock 是标准库中的类,是用Java实现的。

2、synchronized 是通过代码块来控制加锁与解锁的,而 ReentrantLock是通过lock 与 unlock 方法来控制加锁与解锁的,且一定要记得及时去解锁。

3、ReentrantLock 除了普通的加锁、解锁操作之外,还提供了 tryLock方法。不带参数的版本,就是直接去判断这个锁的状态,如果没有线程持有的话,就会进行加锁操作,然后返回true;反之,则会直接返回false,不会进行等待。而带有参数的版本就会等待对应的超时时间去尝试获取锁,如果在超时时间之外了,就会直接返回false。

4、ReentrantLock 本身默认是非公平锁,但是其提供了 公平锁的实现方式。

5、ReentrantLock搭配的等待通知机制,是Condition类,相比wait notify来说功能更强大一些,但是使用的方法是类似的。也是需要先加锁,然后再进行使用的。await 方法是可以更精准地唤醒等待的线程。在有多个等待线程的情况下,signal 可以选择唤醒特定的线程,而Object 类的notify方法在唤醒线程时是相对随机的。

代码演示:

public class Test {public static void main(String[] args) throws InterruptedException {// 下面的锁就是公平锁,是在参数内部传入一个true即可ReentrantLock reentrantLock = new ReentrantLock(true);Condition condition = reentrantLock.newCondition();Thread t1 = new Thread(()->{System.out.println("t线程开始执行");reentrantLock.lock();try {System.out.println("t1线程即将被阻塞");condition.await(); // 阻塞线程t1System.out.println("t1线程被唤醒");} catch (InterruptedException e) {throw new RuntimeException(e);}reentrantLock.unlock();System.out.println("t线程结束执行");});t1.start();Thread.sleep(1000); // 确保t1线程先加锁成功Thread t2 = new Thread(()->{System.out.println("t2线程开始执行");reentrantLock.lock();condition.signal(); // 唤醒正在处于阻塞状态的线程reentrantLock.unlock();System.out.println("t2线程结束执行");});t2.start();System.out.println("main线程结束");}
}

信号量(Semaphore)

信号量主要用来协调进程与线程之间的资源分配。其底层是一个计数器,记录当前资源的可用个数。申请资源,对应的P操作,信号量会减少,释放资源,对应的V操作,信号量会增加。当信号量对应的计数器为0了,此时线程再去申请资源的话,就会线程阻塞。

在Java标准库中,Semaphore 是对应的类。

代码演示:

public class Test {public static void main(String[] args) throws InterruptedException {// 通过参数指定“可用资源”的个数Semaphore semaphore = new Semaphore(5);System.out.println("可用资源的个数:"+semaphore.availablePermits());// 获取资源for (int i = 0; i < 3; i++) {System.out.println("正在获取可用资源~");// 获取"可用资源"semaphore.acquire(); // 也可以传入参数来设定获取的个数,默认1}System.out.println("剩余可用资源的个数:"+semaphore.availablePermits());// 释放资源for (int i = 0; i < 3; i++) {System.out.println("正在释放可用资源~");// 释放“可用资源”semaphore.release(); // 也可以传入参数来设定释放的个数,默认1}System.out.println("最终可用资源的个数:"+semaphore.availablePermits());}
}

运行结果:

注意:当信号量的初始容量为1时,此时就相当于是一把锁。 

CountDownLatch

当一个大的任务被分成多个小任务时,如何知道所有的小任务全部执行完了呢?

1、可以使用 join 的普通等待方法。

2、可以使用计数器来记录当前的完成任务的线程数。

而针对第二种方式,Java标准库中给出了一个类:CountDownLatch。通过构造方法创建出多个任务,当与之对应的线程完成一个任务时,就可以调用 countDown方法来更新计数器的值,最终当计数器的值达到我们的预期时,便可以让主线程继续去执行其它的逻辑了。

代码实现:

public class Test {public static void main(String[] args) throws InterruptedException {int n = 3;CountDownLatch latch = new CountDownLatch(n); // 创建了初始值为n的计数器// 模拟下载的线程System.out.println("等待下载...");for (int i = 0; i < n; i++) {new Thread(() -> {System.out.println("正在下载...");try {// 模拟下载过程Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("下载完成...");latch.countDown(); // 更新计数器,计数器--}).start();}// 等待上述任务全部执行完毕,当计数器为0时,任务执行完毕,脱离阻塞。// 任务没有执行完成时,主线程会处于阻塞状态latch.await();System.out.println("下载完成,正在解压...");System.out.println("成功解压...");}
}

多线程下使用ArrayList、哈希表

普通的ArrayList是没有加锁的,那么在多线程的情境下使用,就会出现线程安全问题,而解决线程安全问题,有三种方法:

1、根据需要自行加锁。

2、无脑将所有的可能会出现线程安全问题的代码全部进行加锁。

3、CopyOnWriteArrayList。

第一二种方式都是采用加锁操作,来确保线程安全。

代码演示:

public class Test {private static final Object locker = new Object();public static void main(String[] args) throws InterruptedException {ArrayList<Integer> arrayList = new ArrayList<>();Thread t1 = new Thread(()->{for (int i = 0; i < 100000; i++) {synchronized (locker) {arrayList.add(i);}}});Thread t2 = new Thread(()->{for (int i = 0; i < 100000; i++) {synchronized (locker) {arrayList.add(i);}}});t1.start();t2.start();t1.join();t2.join();System.out.println(arrayList.size());}
}

上面这种方式就是按需加锁,只在出现线程安全问题的代码部分进行加锁操作,而不是对add这个方法进行无脑的加锁。 

第三种方式,采用了写时拷贝的方法。当线程1在进行"写"操作时,如果线程2来进行"读"操作,这时就让线程2读取原来版本的数据,这样线程1在进行"写"操作时,就不会影响到线程2的"读"操作,也就不会造成线程不安全,那么也就没必要进行加锁操作,从而导致的程序运行效率降低。

缺陷:

1、当数据量过大时,拷贝的成本也就高了很多,这样最终的效率可能还不如加锁的操作呢。

2、当多个线程进行修改操作时,就不能保证线程安全了。因为多个线程进行修改操作,就会拷贝出多份数据,最后怎么合并呢?可能会出现覆盖的问题,其次这是并发执行,因此没发确定顺序的先后,因此就导致了最终数据的不确定性。 

普通的哈希表也是有线程安全问题的。Java标准库中,给我们提供了两个类:Hashtable、ConcurrentHashMap。前者是对整个哈希表进行加锁操作,而后者是对哈希表的每个元素进行加锁操作。

不管是Java标准库中的哈希表,还是我们之前手动实现的哈希表,针对哈希冲突的问题,我们采用的都是数组+链表的方式,而不是去使用线性探测。而对于数组+链表这种方式,只有在同一个链表上面进行修改操作时,才会涉及线程安全问题,因此没必要对整个数组进行加锁。

ConcurrentHashMap相对于Hashtable有以下三个优化的地方:

1、针对数组的加锁操作,变成了对链表进行加锁操作;

2、使用原子类对size进行维护;

3、对于哈希扩容的问题,是分多次进行的,这样每一次所消耗的时间就会比较少。例如,准备扩容时,就可以标记一下,等到下一次在进行put操作时,重新将这个元素所在原链表进行新的哈希映射,当每一个链表都修改完毕时,哈希表的扩容操作也就完成了。

好啦!本期 初始JavaEE篇——多线程(8):JUC的组件 的学习之旅就到此结束啦!我们下一期再一起学习吧!

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

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

相关文章

Java实现JWT登录认证

文章目录 什么是JWT?为什么需要令牌?如何实现?添加依赖&#xff1a;JwtUtils.java&#xff08;生成、解析Token的工具类&#xff09;jwt配置&#xff1a;登录业务逻辑&#xff1a;其他关联代码&#xff1a;测试&#xff1a; 什么是JWT? JWT&#xff08;Json Web Token&…

Meta AR 眼镜团队前负责人加入 OpenAI;visionOS 2.2 Beta 引入超宽屏投屏模式丨 RTE 开发者日报

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real-Time Engagement&#xff09;领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的数据」、「有思考的 文章 …

如何对LabVIEW软件进行性能评估?

对LabVIEW软件进行性能评估&#xff0c;可以从以下几个方面着手&#xff0c;通过定量与定性分析&#xff0c;全面了解软件在实际应用中的表现。这些评估方法适用于确保LabVIEW程序的运行效率、稳定性和可维护性。 一、响应时间和执行效率 时间戳测量&#xff1a;使用LabVIEW的时…

鸢尾博客项目开源

1.博客介绍 鸢尾博客是一个基于Spring BootVue3 TypeScript ViteJavaFx的客户端和服务器端的博客系统。项目采用前端与后端分离&#xff0c;支持移动端自适应&#xff0c;配有完备的前台和后台管理功能。后端使用Sa-Token进行权限管理,支持动态菜单权限&#xff0c;服务健康…

拾光云影 3.3.0 | 高清秒播电视直播,支持IPV4,几千频道

拾光云影是一款Ipv4通用版电视直播APP&#xff0c;界面熟悉但有所改进&#xff0c;操作布局类似TVbox。新增了功能按钮页&#xff0c;提供更多功能。频道清晰&#xff0c;加载速度快&#xff0c;支持港澳台等特殊频道&#xff0c;大部分频道均可秒播。软件内置了直播接口&#…

【HTML】——VSCode 基本使用入门和常见操作

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 零&#xff1a;HTML开发工具VSCode的使用 1&#xff1a;创建项目 2&#xff1a;创建格式模板&#x…

Redis数据库测试和缓存穿透、雪崩、击穿

Redis数据库测试实验 实验要求 1.新建一张user表&#xff0c;在表内插入10000条数据。 2.①通过jdbc查询这10000条数据&#xff0c;记录查询时间。 ②通过redis查询这10000条数据&#xff0c;记录查询时间。 3.①再次查询这一万条数据&#xff0c;要求根据年龄进行排序&#…

zabbix 7.0 安装(服务器、前端、代理等)

https://www.zabbix.com/download 使用上面的地址&#xff0c;按教程执行命令安装

andrular输入框input监听值传递

效果图&#xff1a; step1: E:\projectgood\ajnine\untitled4\src\app\apple\apple.component.html <button mat-button (click)“openDialog()”>Open dialog step2: E:\projectgood\ajnine\untitled4\src\app\apple\apple.component.ts import {Component, inject}…

考公人数攀升?地信、测绘、地质、遥感等专业,能报考哪些单位

近年来&#xff0c;考公人数持续飙升&#xff0c;国考报名人数更逐年攀升。2025年国家公务员考试共有341.6万人通过资格审查&#xff0c;报录比达86:1。国考报名人数再创新高。 国家公务员考试时间安排 地理学相关岗位分析 地信属于地理科学类&#xff0c;测绘类中不包括地信&…

Pr 视频效果:超级键

视频效果/键控/超级键 Keying/Ultra Key 超级键 Ultra Key效果是 Premiere Pro 中功能强大的抠像工具&#xff0c;主要用于绿幕/蓝幕抠像。通过选择要抠除的颜色&#xff08;通常是绿幕或蓝幕的颜色&#xff09;&#xff0c;即可以将该颜色的像素设为透明&#xff0c;实现主体与…

24-11-1-读书笔记(三十一)-《契诃夫文集》(五)下([俄] 契诃夫 [译] 汝龙)生活乏味但不乏魅力。

文章目录 《契诃夫文集》&#xff08;五&#xff09;下&#xff08;[俄] 契诃夫 [译] 汝龙&#xff09;生活乏味但不乏魅力。目录阅读笔记总结 《契诃夫文集》&#xff08;五&#xff09;下&#xff08;[俄] 契诃夫 [译] 汝龙&#xff09;生活乏味但不乏魅力。 休息&#xff0c…

戴尔电脑 Bios 如何进入?Dell Bios 进入 Bios 快捷键是什么?

BIOS&#xff08;基本输入输出系统&#xff09;是计算机启动时运行的第一个程序&#xff0c;它负责初始化硬件并加载操作系统。对于戴尔电脑用户来说&#xff0c;有时可能需要进入 BIOS 进行一些特定的设置调整&#xff0c;比如更改启动顺序、调整性能选项或解决硬件兼容性问题…

【C++】内存管理(二):operator new/delete

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解C的operator new/delete&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 1 new/delete的底层2 new/delete的底层调用顺序3 delete[ ]调用析构函数的次数…

acmessl.cn提供接口API方式申请免费ssl证书

目录 一、前沿 二、API接口文档 1、证书可申请列表 简要描述 请求URL 请求方式 返回参数说明 备注 2、证书申请 简要描述 请求URL 请求方式 业务参数 返回示例 返回参数说明 备注 3、证书查询 简要描述 请求URL 请求方式 业务参数 返回参数说明 备注 4、证…

【docker】docker 环境配置及安装

本文介绍基于 官方存储库 docker 的环境配置、安装、代理配置、卸载等相关内容。 官方安装文档说明&#xff1a;https://docs.docker.com/engine/install/ubuntu/ 虚拟机环境 Ubuntu 20.04.6 LTS 安装步骤 添加相关依赖 sudo apt-get update sudo apt-get install ca-certifi…

机器学习在时间序列预测中的应用与实现——以电力负荷预测为例(附代码)

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 1. 引言 随着数据采集技术的发展&#xff0c;时间序列数据在各个领域中的应用越来越广泛。时间序列预测旨在基于过去的时间数据来…

uniapp+vue加油服务系统 微信小程序

文章目录 项目介绍具体实现截图技术介绍mvc设计模式小程序框架以及目录结构介绍错误处理和异常处理java类核心代码部分展示详细视频演示源码获取 项目介绍 基于微信小程序的加油服务系统设计为微信小程序和后台管理两个服务端&#xff0c;并对此设计相应的功能模块如下&#x…

大数据新视界 -- 大数据大厂之 Impala 资源管理:并发控制的策略与技巧(下)(6/30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

Linux(CentOS)设置防火墙开放8080端口,运行jar包,接收请求

1、查看防火墙状态 systemctl status firewalld 防火墙开启状态 2、运行 jar 包&#xff0c;使用8080端口 程序正常启动 3、使用 postman 发送请求&#xff0c;失败 4、检查端口是否开放&#xff08;需更换到 root 用户&#xff09; firewall-cmd --zonepublic --query-por…