[Java EE] 多线程(九):ReentrantLock,Semaphore,CountDownLatch与线程安全的集合类(多线程完结)

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:🍕 Collection与数据结构 (91平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀Java EE(94平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
在这里插入图片描述
感谢关注,欢迎点赞和收藏~~~

ReentrantLock,Semaphore,CountDownLatch与线程安全的集合类

  • 1. 手动锁ReentrantLock
    • 1.1 用法
    • 1.2 ReentrantLock与synchronized的区别
  • 2. 信号量Semaphore
  • 3. CountDownLatch
  • 4. 线程安全的集合类
    • 4.1 多线程情况下使用ArrayList
    • 4.2 多线程使用队列
    • 4.3 多线程环境下使用哈希表
      • 4.3.1 Hashtable
      • 4.3.2 ConcurrentHashMap(高频面试题)

1. 手动锁ReentrantLock

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

1.1 用法

  • lock(),加锁,如果获取不到锁就会死等.
  • tryLock(),枷锁,如果一定时间内获取不到锁就放弃加锁.
  • unlock(),解锁.
    在进行加锁解锁操作的时候,为了防止加锁之后未解锁的情况,我们在使用ReentrantLock的时候,一般使用try-finally结构来完成.未解锁操作并不是程序员忘记写unlock造成的,而是中间出现了一些例如在执行到某个时候直接return,或者在某些时候抛出异常,使得程序终止的操作.这时候unlock操作就执行不到了.而finally可以完美地解决这个问题.

代码如下:

public class Demo29 {public static void main(String[] args) {ReentrantLock reentrantLock = new ReentrantLock();//公平reentrantLock.lock();try {//...}finally {reentrantLock.unlock();}}
}

1.2 ReentrantLock与synchronized的区别

  • ReentrantLock需要手动加锁和手动解锁,而synchronized自动加锁解锁.
  • synchronized在申请失败的时候,会死等,而ReentrantLock可以使用tryLock方法来限制等待的时间.
  • synchronized是非公平锁,而ReentrantLock可以通过构造方法来规定这把锁是否是公平锁.
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
ReentrantLock reentrantLock = new ReentrantLock(true);//公平
ReentrantLock reentrantLock1 = new ReentrantLock(false);//非公平
  • ReentrantLock拥有强大的唤醒机制,他可以指定线程唤醒,而synchronized如果有多个线程在wait的时候,notify只能唤醒随机的其中一个线程.

2. 信号量Semaphore

信号量,就是一个表示资源剩余个数的量,本质上就是一个计数器.

举例说明:停车场自动化停车
在每一个停车场的起杆的地方,都会显示当前剩余车位:xxx,只要有一辆车进入停车场,显示器上的停车位就会-1(称为p操作),有一辆车出来,就会+1(称为v操作),当车位已满的时候,停车场的杆就不会自动抬起.想要进来的车就要阻塞等待,等待有车从停车场出来才可以进去.
在这里插入图片描述

Semaphore的操作都是原子的,可以在多线程环境下直接使用.
代码实例:

  • acquire用来申请资源,release用来释放资源.
class demo{public static void main(String[] args) throws InterruptedException {Semaphore semaphore = new Semaphore(4);for (int i = 0; i < 4; i++) {semaphore.acquire();System.out.println("申请第"+(i+1)+"个资源");}for (int i = 0; i < 4; i++) {semaphore.release();System.out.println("释放第"+(i+1)+"个资源");}}
}

运行结果:
在这里插入图片描述

  • 如果在资源使用完之后,再去申请资源,就会阻塞等待.
class demo{public static void main(String[] args) throws InterruptedException {Semaphore semaphore = new Semaphore(4);for (int i = 0; i < 4; i++) {semaphore.acquire();System.out.println("申请第"+(i+1)+"个资源");}semaphore.acquire();}
}

运行结果:
在这里插入图片描述
我们看到,进行并未结束.

3. CountDownLatch

这个类是一个比较实用的工具类,他的主要功能就是:当一个任务被拆分成许多部分让多个线程执行的时候,就可以通过这个类来判断任务是否全部执行完毕.
就比如我们经常用到的一个下载工具:IDM下载器,这个下载工具的下载速度非常快,就是因为这个下载工具会把下载任务拆分成多个任务,等待所有下载任务全部结束之后,载合并下载结果,这时候就需要有一个工具来判断所有任务是否全部下载完毕.

举例说明:跑步比赛
10个选手同时起泡,有的跑的慢,有的跑的快,但是裁判必须等到所有运动员全部通过终点之后才可以结束比赛.

代码示例:

  • 使用countDown方法领取任务.
  • 使用await方法判断所有线程是否执行完毕.
import java.util.concurrent.CountDownLatch;public class Demo30 {public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(5);//任务被拆分成了5个部分for (int i = 0; i < 5; i++) {//把任务分配给5个线程int finalI = i;Thread thread = new Thread(()->{try {Thread.sleep((finalI +1)*1000);} catch (InterruptedException e) {throw new RuntimeException(e);}latch.countDown();//获取任务System.out.println("任务"+(finalI+1)+"结束");});thread.start();}latch.await();//等待所有线程结束任务System.out.println("所有线程任务结束");}
}

运行结果:
在这里插入图片描述

4. 线程安全的集合类

原来的集合类,大多数都是线程不安全的.但是其中有几个线程是安全的.比如:Vector,Stack,HashTable.这些集合的方法都自带synchronized,都是被synchronized修饰的.但是加了锁也不一定安全,需要具体问题具体分析.

4.1 多线程情况下使用ArrayList

  1. 自己使用同步机制,synchronized或者ReentrantLock.
  2. Collections.synchronizedList(new ArrayList)
    这个操作相当于给ArrayList套了一层壳.相当于之后对ArrayList的操作都带上了synchronized修饰.
    上面两种操作都是有加锁的操作,下面我们介绍一种不加锁的操作.
  3. 使用CopyOnWriteArrayList.
    CopyOnWrite即写时拷贝容器.

如果我们想要修改一个容器中的值的时候,如果直接进行修改,比如想要修改两个数据,一个线程刚好修改完第一个数据的时候,有第二个线程想要来读取修改后的数据,这时候就读到的是一种"中间结果",不够准确.

这时候就需要引入写时拷贝容器:

  • 当我们往一个容器中添加或者修改数据的时候,不直接修改当前容器,而是先拷贝当前容器,之后在复制出的容器中进行修改.
  • 在修改完成之后,将原容器的引用指向修改后的容器.
    这样如果在有线程去读取数据的时候,如果修改未完成的时候,读取的就是原容器的数据,修改完成之后,就是读取新容器的数据了.所以CopyOnWrite容器采用的便是读写分离思想.

举例说明:不停机更新
在我们玩一个游戏,比如王者荣耀的时候,经常会出现不停机更新这样的现象.在更新的时候,并不会影响用户的游戏体验,在一场游戏结束之后,自动获取游戏更新内容.
在这里插入图片描述

4.2 多线程使用队列

  1. ArrayBlockingQueue 基于数组实现的阻塞队列
  2. LinkedBlockingQueue 基于链表实现的阻塞队列
  3. PriorityBlockingQueue 基于堆实现的带优先级的阻塞队列
  4. TransferQueue 最多只包含⼀个元素的阻塞队列

4.3 多线程环境下使用哈希表

HashMap本身是线程不安全的.在多线程环境下使用哈希表可以使用:

  • Hashtable
  • ConcurrentHashMap

4.3.1 Hashtable

只是简单地把关键方法加上了synchronized.这就相当于直接对Hashtable对象本身直接加锁.
如果一个哈希表只有一把锁,别说在操作同一个哈希桶,即使在不同的线程操作不同的哈希桶的时候,也会产生阻塞,这样的效率是非常低的.而且一旦触发扩容,就会有大量拷贝的操作,这样的效率是非常低的.
在这里插入图片描述

4.3.2 ConcurrentHashMap(高频面试题)

相比于Hashtable做出了一些优化.

  • 优化1:ConcurrentHashMap对每一个哈希桶都使用了synchronized进行加锁,这就大大降低了锁冲突的概率.或许有的人会想,每个哈希桶都加锁,不是加大了加锁开销吗,其实不是,当没有线程与当前哈希桶进行锁竞争的时候,加锁的锁只是一个偏向锁,开销也没有大多少.反而降低锁冲突的概率收益会很明显.
    在这里插入图片描述

  • 优化2:充分利用了CAS特性.比如size属性就通过CAS来更新.避免出现重量级锁的情况.

  • 优化3: 优化了扩容方式:化整为零

    • 当发现需要扩容的时候,就会创建一个新的数组出来,同时只拷贝几个元素过去.
    • 扩容期间,新数组和老数组同时存在.
    • 后续的每个对ConcurrentHashMap的操作都会拷贝几个元素过去.总的来说不是一次性全部拷贝完成,而是分多次拷贝.
    • 之后每次插入新元素的时候都直接插入新数组中.
    • 当拷贝完成最后一个元素的时候,老数组就会被删除.

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

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

相关文章

PE文件(四)FileBuffer-ImageBuffer作业

C语言实现如下功能 2.编写一个函数&#xff0c;将RVA的值转换成FOA 将文件加载到内存时&#xff0c;已知一个数据在内存中的地址&#xff0c;将此地址转化成文件在硬盘上时的相对于文件起始地址的文件偏移地址。即将虚拟内存偏移地址转换成文件偏移地址。 说明&#xff1a;这里…

记一次DNS故障导致用户无法充值的问题(上)

背景&#xff1a; 刚刚过去了五一劳动节&#xff0c;回来后一上班接到客服运营团队反馈的节日期间的问题&#xff0c;反馈有部分用户无法充值。拿到的反馈资料有&#xff1a; 无法充值操作视频、问题时间、手机机型、手机网络情况。 1、从视频中看到用户点击支付后没有任何反…

Linux系统使用Docker安装青龙面板并实现远程访问管理面板

文章目录 一、前期准备本教程环境为&#xff1a;Centos7&#xff0c;可以跑Docker的系统都可以使用。本教程使用Docker部署青龙&#xff0c;如何安装Docker详见&#xff1a; 二、安装青龙面板三、映射本地部署的青龙面板至公网四、使用固定公网地址访问本地部署的青龙面板 青龙…

聊聊 ASP.NET Core 中间件(三):如何创建自己的中间件?

前言 本质上&#xff0c;中间件类也是一个普通的 .NET 类&#xff0c;它不需要继承任何父类或者实现任何接口。 但是有几个约定&#xff1a; 需要有一个构造方法构造方法至少要有一个 RequestDelegate 类型的参数&#xff0c;用来指向下一个中间件。需要定义一个名字为 Invo…

嵌入式linux学习第一天

参考正点原子Linux开发文档。记录下知识点。 Shell 基本操作 前面我们说 Shell 就是“敲命令”&#xff0c;那么既然是命令&#xff0c;那肯定是有格式的&#xff0c;Shell 命令的格式 如下&#xff1a; command -options [argument] command: Shell 命令名称。 options&…

雷军-2022.8小米创业思考-6-互联网七字诀之专注:有所为,有所不为;克制贪婪,少就是多;一次解决一个最迫切的需求

第六章 互联网七字诀 专注、极致、口碑、快&#xff0c;这就是我总结的互联网七字诀&#xff0c;也是我对互联网思维的高度概括。 专注 从商业角度看&#xff0c;专注就是要“把鸡蛋尽量放在一个篮子里”。这听起来似乎有些不合理&#xff0c;大家的第一反应可能是“风险会不会…

JUC-synchronized练习-交替打印ABC

今天来练习一下synchronized 简单来利用synchronized实现一个字符串的交替打印 主要的实现设置一个全局的变量state&#xff0c;线程执行通过不断累加state&#xff0c;根据state对三取余的结果来判断该线程是否继续执行还是进入等待。并通过synchronized锁住一个共享变量loc…

js 图片渐变

1. 点击图片&#xff0c;使其渐变为另一张图片 通过定义keyframes来创建一个淡入淡出的动画效果。当图片被点击时&#xff0c;先添加淡出动画使图片透明度从0渐变到1&#xff0c;然后在1秒后切换图片源并添加淡入动画使新图片透明度从0渐变到1&#xff0c;实现图片渐变效果。 …

自动化机器学习——贝叶斯优化

自动化机器学习——贝叶斯优化 贝叶斯优化是一种通过贝叶斯公式推断出目标函数的后验概率分布&#xff0c;从而在优化过程中不断地利用已有信息来寻找最优解的方法。在贝叶斯优化中&#xff0c;有两个关键步骤&#xff1a;统一建模和获得函数的优化。 1. 统一建模 在贝叶斯优…

.双链表.

题目&#xff1a; 实现一个双链表&#xff0c;双链表初始为空&#xff0c;支持 55 种操作&#xff1a; 在最左侧插入一个数&#xff1b;在最右侧插入一个数&#xff1b;将第 k&#x1d458; 个插入的数删除&#xff1b;在第 k&#x1d458; 个插入的数左侧插入一个数&#xf…

新型中医揿针如何降血糖呢?

点击文末领取揿针的视频教程跟直播讲解 “新型针贴”专用揿针是为“埋针疗法”特制治的一种特殊针具&#xff0c;它是古代针刺留针方法的发展。具体来说&#xff0c;它是将特制针具刺入皮内&#xff0c;固定后留置一定时间&#xff0c;利用其持续微弱的刺激作用来治疗疾病的一…

做抖音小店需要注意什么?这几点很多人不知道,看完防踩坑

大家好&#xff0c;我是电商笨笨熊 抖音小店虽然推出了一段时间&#xff0c;但是依旧有新手玩家陆陆续续加入其中&#xff1b; 对于很多新手来说&#xff0c;只看到了其中红利&#xff0c;但却没有看到其中包含的一些运营小细节&#xff0c;且这些细节决定你店铺未来发展&…

【redis】Redis数据类型(四)Set类型

目录 Set类型介绍使用场景 Set类型数据结构set的单个元素的添加过程IntSet哈希表内存结构 常用命令SADD示例 SREM示例 SMEMBERS示例 SISMEMBER示例 SCARD示例 SMOVE示例 SPOP示例 SRANDMEMBER示例 SINTER示例 SINTERSTORE示例 SUNION示例 SUNIONSTORE示例 SDIFF示例 SDIFFSTORE…

每日一题(PTAL2):列车调度--贪心+二分

选择去维护一个最小区间 代码1&#xff1a; #include<bits/stdc.h> using namespace std; int main() {int n;cin>>n;int num;vector <int> v;int res0;for(int i0;i<n;i){cin>>num;int locv.size();int left0;int rightv.size()-1;while(left<…

Go语言fmt包深度探索:格式化输入输出的利器

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 &#x1f3ad; 引言一、基础输出函数fmt.Print与fmt.Println&#x1f4cc; fmt.Print&#xff1a;纯粹输出&#xff0c;不带换行&#x1f4cc; fmt.Println&#xff1a;输出后自动添加换行符 二、格式化输出fmt.Printf&…

在Java中如何有效地处理内存泄露

在Java中&#xff0c;处理内存泄露有多种方法&#xff0c;以下是其中三种常见的方法及其原理和适用场景&#xff1a; ## 1. 合理使用垃圾回收机制 Java中的垃圾回收机制&#xff08;Garbage Collection&#xff0c;GC&#xff09;是一种自动化的内存管理技术&#xff0c;它可以…

代码随想录day19day20打卡

二叉树 1 二叉树的最大深度和最小深度 最大深度已经学习过了&#xff0c;实质就是递归的去判断左右子节点的深度&#xff0c;然后对其进行返回。 附加两个学习的部分&#xff1a; &#xff08;1&#xff09;使用前序遍历的方法求解 int result; void getdepth(TreeNode* nod…

保研面试408复习 1——操作系统、计网、计组

文章目录 1、操作系统一、操作系统的特点和功能二、中断和系统调用的区别 2、计算机组成原理一、冯诺依曼的三个要点二、MIPS&#xff08;每秒百万条指令&#xff09;三、CPU执行时间和CPI 3、计算机网络一、各个层常用协议二、网络协议实验——数据链路层a.网络速率表示b.数据…

酷得智能电子方案 早教学习机

早教学习机是用户友好的&#xff0c;易于操作&#xff0c;同时要确保内容的科学性和适宜性&#xff0c;以促进儿童的健康成长和智力发展。 通常包括以下几个方面&#xff1a; 1.年龄分级内容&#xff1a;软件会根据儿童的不同年龄段提供相应的教育内容&#xff0c;从新生儿到…

Qt---day2-信号与槽

1、思维导图 2、 拖拽式 源文件 #include "mywidget.h" #include "ui_mywidget.h" MyWidget::MyWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::MyWidget) { ui->setupUi(this); //按钮2 this->btn2new QPushButton("按钮2",th…