JAVAEE初阶相关内容第十二弹--多线程(进阶)

目录

一、JUC的常见类

1、Callable接口

1.1callable与runnable

1.2代码实例

(1)不使用Callable实现

(2)使用Callable实现

1.3理解Callable

1.4理解FutureTask

2、ReentrantLock

2.1ReentrantLock的用法

2.2ReentrantLock优势

3、原子类

4、Semaphore信号量

4.1理解信号量

5、CountDownLatch

5.1理解CountDownLatch

5.2主要的两个方法

二、线程安全的集合类

1、多线程环境下使用ArrayList

2、多线程环境下使用队列

(1)ArrayBlockingQueue

(2)LinkedBlockingQueue

(3)PriorityBlockingQueue

(4)TransferQueue

3、多线程环境下使用哈希表【重点】

3.1Hashtable

3.2ConcurrentHashMap


一、JUC的常见类

JUC:java.util.concurrent

各种集合类,scanner、random...

concurrent 并发,放了很多并发编程(多线程)相关组件

1、Callable接口

1.1callable与runnable

类似于Rannable 用来描述一个任务

Rannable用来描述一个任务,描述的任务没有返回值。

Callable也是用来描述一个任务,描述的任务有返回值。

如果需要使用一个线程单独的计算某个结果来,此时使用Callable是比较合适的。

1.2代码实例

创建线程计算1到1000的累加和

(1)不使用Callable实现

创建一个类Result,包含一个sum表示最终结果,lock表示线程同步使用的锁对象。

main方法中先创建Result实例,然后创建一个线程t,在线程内部计算1到1000的累加和

主线程同时使用wait等待线程t计算结束(注意如果执行到wait之前,线程t已经计算完了,就不必等待了)

当线程t计算完毕后,通过notify唤醒主线程,主线程再打印结果

代码:

class Result{public int sum = 0;public Object lock = new Object();
}
public class ThreadD29_1 {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 = 0; 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);}}
}
(2)使用Callable实现
    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 = 0; 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);}

对上述代码的一些理解:

(1)不能直接把callable传到Thread中

应该为:

FutureTask:未来的一个任务

get方法就是获取结果

get会发生阻塞,直到callable执行完毕,get才阻塞完成,才获取到结果。

1.3理解Callable

(1)callable和Runnable是相对的,都是描述一个“任务”。Callable描述的是带有返回值的任务Runnable描述的是带有返回值的任务。

(2)Callable通常需要搭配FutureTask来使用。FutureTask用来保存Callabe的返回结果。因为Callable往往是再另一个线程中执行的,啥时候执行完并不确定。

(3)FutureTask就可以负责这个等待结果出来的工作。

1.4理解FutureTask

例如我们在商场吃饭,点餐好了之后后厨就开始做饭,同时在窗口营业员会给你一张“取餐码”,这个“取餐码”就是FutureTask,后面我们可以随时拿着这个取餐码去查看自己的餐食有没有做出来。

2、ReentrantLock

标准库给我们提供的另一种锁。可重入互斥锁,和synchronized定位类似,都是用来实现互斥效果,保证线程安全。

synchronized是直接基于代码的方式来加锁解锁的。

ReentrantLock更传统,使用lock和unlock方法加锁解锁。(最大的问题是unlock可能会执行不到)

建议把unlock放到finally中

2.1ReentrantLock的用法

(1)lock() :加锁,如果获取不到锁就死等【存在return或者异常都可能导致不能顺利执行解锁】

(2)trylock():加锁,如果获取不到锁,等待一定的时间之后就放弃加锁

(3)unlock():解锁

2.2ReentrantLock优势

(1)提供了公平锁版本的实现

(2)ReentranrLock提供了更加灵活的等待方式:tryLock

对于synchronized来说提供的加锁操作就是“死等”,只要获取不到锁,就一直阻塞等待。

无参数版:能加锁就加,加不上锁就放弃。

有参数版:指定了超时时间,加不上锁就等待一会,如果等一会时间到了也没加上就放弃。

(3)ReentrantLock提供了一个更强大,更方便的等待通知机制

synchronized搭配的是wait、notify的时候随机唤醒一个线程。

ReentrantLock搭配的是一个Condition类,进行唤醒的时候可以唤醒指定的线程。

虽然RentrantLock有有一定的优势,但是在一般情况下还是使用synchronized。

3、原子类

原子类内部是使用CAS实现的,所以性能要比加锁实现i++高很多,原子类有以下几个:

原子类
AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicLong
AtomicReference
AtomicStampedReference
AtomiInteger举例--常见方法
addAndGet(int delta);i +=delta;
decrementAndGet();--i;
getAndDecrement();i--;
incremenrAndGet();++i;
getAndIncrement();i++;

基于CAS,确实是更高效的解决了线程的安全问题,但是CAS不能代替锁,CAS的适用范围有限,不像锁适用的范围广。

4、Semaphore信号量

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

4.1理解信号量

信号量可以和生活实际相联系。

假设现在在A停车场,当前的车位有100个,表示有100个可用资源,当有车开进去的时候就相当于申请了一个可用资源,,可用车位就-1(这个称为信号量的P操作)。当有车从A停车场开出去的时候,就相当于释放了一个可用资源,可用车位就+1(这个称为信号量的V操作),如果计数器的值已经为0了,还尝试申请资源,就会阻塞等待,直到有其他的线程释放资源。

Semaphore的PV操作中的加减计数操作都是原子的,可以在多线程下直接使用。

实际开发中,虽然锁是最常用的,但是信号量偶尔也会用到,主要是看实际的需求场景。代码中也是可以使用Semaphore来实现类似于锁的效果,来保证线程安全的。

锁可以视为是计数器为1的信号量。二元信号量,锁是信号量的一种特殊情况,信号量就是锁的一般表达。

5、CountDownLatch

简单了解即可,使用的不是特别多,【特定场景】

5.1理解CountDownLatch

首先举一个例子:跑步比赛。开始的时间明确,结束的时间不明确。为了等待这和个跑步比赛结束,引入CountDownLatch。

5.2主要的两个方法

(1)await(wait是等待,a=>all)主线程来调用这个方法

(2)countDown 表示选手冲过了终点线

CountDownLatch在构造的时候,指定一个计数(选手的个数)。

例如指定四个选手进行比赛,初始情况下调用await就会阻塞,每个选手冲过终点就会调用countDown方法。

前三次调用countDown,await没有任何影响

第四次调用countDown,await就会被唤醒返回(解除阻塞)此时就可以认为比赛就结束了。

在实际的开发中,CountDownLatch也是有很多使用场景的,比如下载一个大文件。(视频文件好几个G,把一个大文件切分成好多个小块安排多个线程分别下载)

二、线程安全的集合类

原来的集合类,大部分都是线程不安全的

Vector、Stack、HashTable是线程安全的(不建议用),其他的集合类不是线程安全的。

1、多线程环境下使用ArrayList

(1)自己使用同步机制(synchronnized 或者ReentrantLock)[常见]

(2)Collentions.synchronnizedList(new ArrayList);

这里会提供一些ArrayList相关的方法,同时是带锁的,使用这个方法把 集合类 套一层。

synchronizedList是标准库提供的一个基于synchronized进行线程同步的List。

synchronizedList的关键操作上都带有synchronnized

(3)使用CopyOnWriteArrayList 

CopyOnWrite容器即写时复制的容器“COW”也叫“写时拷贝”

如果针对这个ArrayList进行读操作,不做任何额外的工作。如果进行写操作,则拷贝一份新的ArrayList,针对新的进行修改,修改过程中如果有读操作,就继续读旧的这份数据,当修改完毕,使用新的替换旧的(本质上就是一个引用之间的赋值,原子的)

很明显,这种方案优点是不需要加锁,缺点是要求这个ArrayList不能太大,只能适用于这种数组比较小的情况下

服务器的程序进行配置和维护,一个程序可能包含很多子功能,有个功能想要使用,有的功能不想,有的希望应用到不同形态...就可以使用一系列的“开关选项”来控制当前程序的工作状态。

服务器程序的配置文件可能会需要进行修改,修改配置可能就需要重启服务器才能生效,重启服务器的成本还高。因此很多服务器都提供了“热加载” reload。

理解热加载?

不重启服务器实现配置更新。新的配置放在新的对象中,加载过程里,请求依然基于旧的配置工作。当新对象加载完成实验新的对象替代旧对象(替换完成旧对象释放)

小结:

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

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

2、多线程环境下使用队列

(1)ArrayBlockingQueue

基于数组阻塞队列实现

(2)LinkedBlockingQueue

基于链表实现的阻塞队列

(3)PriorityBlockingQueue

基于堆实现的阻塞队列

(4)TransferQueue

最多只包含一个元素的阻塞队列

3、多线程环境下使用哈希表【重点】

HashMap本身不是线程安全的

在多线程环境下使用哈希表可以使用:HashTable、ConcurrentHashMap

更推荐使用的是ConcurrentHashMap,更优化的线程安全哈希表

3.1Hashtable

只是简单的把关键方法加上了关键字synchronized这相当于直接对Hashtable对象本身加锁

如果多线程访问同一个Hashtable就会直造成锁冲突

size属性也是通过synchronized来控制同步的,也是比较慢的

一旦触发扩容,就由该线程完成整个扩容过程,这个过程会涉及到大量的元素拷贝,效率很低

如图所示,元素1和元素2在同一个链表上,如果线程A修改元素1,线程B修改元素2,此时就会有线程安全问题。

如果线程A修改元素3,线程B修改元素4,这个就相当于是多个线程修改不同的变量

3.2ConcurrentHashMap

相比于Hashtable做出了一系列的改进和优化【以Java 1.8为例】

(1)最大的优化之处在于CurrentHashMap相比于Hashtable大大缩小了锁冲突的概率,把一把大锁转换成多把小锁。

HashTable做法是直接在方法上加synchronized,等于是给this加锁,只要操作哈希表上的任意元素都会产生加锁,也就都可能发生锁冲突。但是实际上,仔细思考不难发现,基于哈希表的特点,有些元素在进行并发操作的时候,是不会产生线程安全问题的,也不需要使用锁来控制。

上述谈到的情况是针对JDK1.8及其以后的情况,在1.7和之前,ConcurrentHashMap使用的是分段锁。分段锁本质上也是缩小锁的范围,从而降低锁冲突的概率。但是这种做法不够彻底。一方面粒度不够细,另一方面代码实现也更繁琐。

(2)ConcurrentHashMap做了一个激进的操作

针对读操作不加锁,只针对写操作加锁。【但是使用了volatile保证内存读取结果加锁方式依然是用的sunchronized,但是不是锁的整个对象,而是“锁桶”,用每个链表的头结点作为锁对象,大大降低了锁冲突的概率。

读与读之间无冲突

写与写之间有冲突

读与写之间也没有冲突

很多场景下,读写之间不加以控制的话,可能就会读到一个写了一半的结果,如果操作不是原子的,此时读就可能会读到写了一半的数据,相当于脏读。

(3)ConcurrentHashMap内部充分的使用CAS,通过这个也来进一步削减加锁操作的数目。比如维护元素个数

(4)针对扩容,采取“化整为零”的方式

HashMap/HashTable扩容:

创建一个更大的数组空间哦,把旧的数组上的链表上的每个元素搬运到新的数组上(插入+删除)这个扩容会在某次put的时候进行触发。如果元素个数特别多,就会导致这样的搬运操作,比较耗时,就会出现某次put比平时put卡很多倍。

ConcurrentHashMap扩容:

扩容采取的是每次搬运一小部分元素的方式,创建新的数组,旧的数组也保留;每次put操作,就会往新数组中添加,同时进行一部分搬运(把一小部分旧的元素搬运到新数组上),每次get的时候,旧的数组和新数组都查询,每次remove的时候,只是把元素删了就可以。

下一篇将更新这一部分的相关面试题~

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

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

相关文章

【面试题】Js数组去重都有哪些方法?

前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 表妹一键制作自己的五星红旗国庆头像&#xff0c;超好看 1. indexOf 定义&#xff1a; indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置…

springboot整合sentinel完成限流

1、直入正题&#xff0c;下载sentinel的jar包 1.1 直接到Sentinel官网里的releases下即可下载最新版本&#xff0c;Sentinel官方下载地址&#xff0c;直接下载jar包即可。不过慢&#xff0c;可能下载不下来 1.2 可以去gitee去下载jar包 1.3 下载完成后&#xff0c;进行打包…

【办公小神器】:快速批量转换Word、Excel、PPT为PDF脚本!

文章目录 ✨哔哩吧啦✨脚本使用教程✨温馨小提示设置&#x1f4da;资源领取 专栏Python零基础入门篇&#x1f525;Python网络蜘蛛&#x1f525;Python数据分析Django基础入门宝典&#x1f525;小玩意儿&#x1f525;Web前端学习tkinter学习笔记Excel自动化处理 ✨哔哩吧啦 前…

[pai-diffusion]pai的easynlp的clip模型训练

EasyNLP带你玩转CLIP图文检索 - 知乎作者&#xff1a;熊兮、章捷、岑鸣、临在导读随着自媒体的不断发展&#xff0c;多种模态数据例如图像、文本、语音、视频等不断增长&#xff0c;创造了互联网上丰富多彩的世界。为了准确建模用户的多模态内容&#xff0c;跨模态检索是跨模态…

Ctfshow web入门 代码审计篇 web301-web310 详细题解 全

CTFshow 代码审计 web301 下载的附件的目录结构如下&#xff1a; 开题后界面&#xff0c;看见输入框&#xff0c;感觉是sql。 大概浏览一遍源码&#xff0c;我们可以发现在checklogin.php文件中有无过滤的SQL语句&#xff0c;SQL注入没得跑了。 这题SQL注入有三种做法。 方法一…

Linux:GlusterFS 集群

GlusterFS介绍 1&#xff09;Glusterfs是一个开源的分布式文件系统,是Scale存储的核心,能够处理千数量级的客户端.在传统的解决 方案中Glusterfs能够灵活的结合物理的,虚拟的和云资源去体现高可用和企业级的性能存储. 2&#xff09;Glusterfs通过TCP/IP或InfiniBand RDMA网络链…

Everything + Cpolar,打造在线搜索的终极神器

文章目录 前言1. 下载安装注册cpolar2. Everything安装和设置2.1 进入Everything官网进行下载2.2 对Everything文件进行设定 3. 创建cpolar内网穿透隧道4. 公网访问测试Everything5. 固定连接公网地址 前言 你还在用Windows资源管理器自带的搜索工具来搜索文件吗&#xff1f;这…

轮换对称性

二重积分 普通对称性–D关于 y x yx yx对称&#xff1a; ∬ D f ( x , y ) d σ { 2 ∬ D 1 f ( x , y ) d σ f ( x , y ) f ( y , x ) 0 f ( x , y ) − f ( y , x ) \iint_{D}f(x,y)d\sigma\begin{cases} 2\iint_{D_1}f(x,y)d\sigma\ \ \ \ \ \ f(x,y)f(y,x) \\ 0 \ \…

OpenStack创建云主机并连接CRT

文章目录 OpenStackT版创建云主机并连接CRT命令行操作&#xff08;1&#xff09;创建镜像&#xff08;2&#xff09;创建实例&#xff08;3&#xff09;创建网络创建内网创建外网 &#xff08;4&#xff09;创建安全组&#xff08;5&#xff09;创建路由&#xff08;6&#xff…

1952-2018年中国各省份人均GDP数据(消涨处理)

1952-2018年中国各省份人均GDP数据&#xff08;消涨处理&#xff09; 1、时间&#xff1a;1952-2018年 2、范围&#xff1a;30省市 3、指标&#xff1a;人均GDP 4、来源&#xff1a;《新中国60周年统计汇编》和各省年鉴 5、指标解释&#xff1a; 过程为环比人均GDP指数转…

YSA Toon (Anime/Toon Shader)

这是一个Toon着色器/Cel阴影着色器,用于Unity URP 此着色器的目的是使角色或物体阴影实时看起来尽可能接近真实的动画或卡通效果 可以用于游戏,渲染,插图等 着色器特性,如:面的法线平滑、轮廓修复、先进的边缘照明、镜面照明、完全平滑控制 这个文档包括所有的功能https:/…

Eclipse ABAP ADT 集成详细安装教程

最近看到网上有个源码使用CDS做的&#xff0c;然后看了一下原来还可以用eclipse&#xff0c;趁热打铁&#xff0c;试了一把&#xff0c;最后成功了&#xff0c;中间可能会有一些报错&#xff0c;可以自己慢慢解决&#xff0c;大概就是这样的。 SAP的开发&#xff0c;有三种开发…

Java————List

一 、顺序表和链表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c; 常见的线性表&#xff1a;顺序表、链表、栈、队列… 线性表在逻辑上是线性结构&#xff0c;也就说是连续的一条直…

微信小程序与idea后端如何进行数据交互

交互使用的其实就是调用的req.get(url)方法 进行路径访问&#xff0c;你要先保证自己的springboot项目已经成功运行了&#xff1a; 如下&#xff1a; 如何交互的&#xff1f; 微信小程序&#xff1a;如下为index.js页面 在onLoad()事件中调用方法Project.findAllCities() 要…

贝叶斯滤波计算4d毫米波聚类目标动静属性

机器人学中有些问题是二值问题&#xff0c;对于这种二值问题的概率评估问题可以用二值贝叶斯滤波器binary Bayes filter来解决的。比如机器人前方有一个门&#xff0c;机器人想判断这个门是开是关。这个二值状态是固定的&#xff0c;并不会随着测量数据变量的改变而改变。就像门…

rv1126-rv1109-test

测试指令 播放音频:aplay aigei.wav 测试时间: 查看系统时间:date 设置时间:date -s "2023-09-21 16:00:00" 设置芯片时间:hwclock -w 查看芯片时间:hwclock 测试背光: echo 0 > sys/class/backlight/backlight/brightness echo 50 > sys/class/backlig…

期权如何交易?期权如何做模拟交易?

买卖期权的第一步就是要有期权账户&#xff0c;国内的期权品种有商品期权和ETF期权以及股指期权&#xff0c;每种的开户方式和要求都不同&#xff0c;下文为大家介绍期权如何交易&#xff1f;期权如何做模拟交易&#xff1f; 一、期权交易需要开立一个期权账户&#xff0c;可以…

【Spring Boot 源码学习】OnBeanCondition 详解

Spring Boot 源码学习系列 OnBeanCondition 详解 引言往期内容主要内容1. getOutcomes 方法2. getMatchOutcome 方法2.1 ConditionalOnBean 注解处理2.2 ConditionalOnSingleCandidate 注解处理2.3 ConditionalOnMissingBean 注解处理 3. getMatchingBeans 方法 总结 引言 上篇…

实战演练 | Navicat 常用功能之转储与运行 SQL 文件

数据库管理工作中&#xff0c;"转储 SQL 文件"和"运行 SQL 文件"是两个极为常见操作。一般来说&#xff0c;用户使用数据库管理工具或命令行工具来完成。Navicat 管理开发工具中的“转储 SQL 文件”和“运行 SQL 文件”功能具有直观易用的界面、多种文件格…

Linux内核源码分析 (B.x)Linux物理内存的初始化

Linux内核源码分析 (B.x)Linux物理内存的初始化 文章目录 Linux内核源码分析 (B.x)Linux物理内存的初始化一、DDR简介二、内存节点三、内存管理区域ZONE四、 struct zone五、 struct page六、mem_map数组七、伙伴系统简介八、迁移类型九、内存初始化十、总结 一、DDR简介 详细可…