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

news2025/1/12 0:57:35

目录

一、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(){
            @Override
            public 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>() {
            @Override
            public 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.coloradmin.cn/o/1030396.html

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

相关文章

BaseMapper 中的方法

BaseMapper 中的方法&#xff1a; 插入 int insert(T entity) - 插入一条记录。 删除 int deleteById(Serializable id) - 根据主键ID删除记录。 int deleteById(T entity) - 根据实体对象&#xff08;ID&#xff09;删除记录。 int deleteByMap(Map<String, Object> …

快速用Python进行数据分析技巧详解

概要 一些小提示和小技巧可能是非常有用的&#xff0c;特别是在编程领域。有时候使用一点点黑客技术&#xff0c;既可以节省时间&#xff0c;还可能挽救“生命”。 一个小小的快捷方式或附加组件有时真是天赐之物&#xff0c;并且可以成为真正的生产力助推器。所以&#xff0…

【SpringCloud】微服务技术栈入门1 - 远程服务调用、Eureka以及Ribbon

目录 远程服务调用RestTemplate Eureka简要概念配置 Eureka 环境设置 Eureka ClientEureka 服务发现 Ribbon工作流程配置与使用 Ribbon饥饿加载 远程服务调用 RestTemplate RestTemplate 可以模拟客户端来向另外一个后端执行请求 黑马给出的微服务项目中&#xff0c;有两个 …

漏刻有时数据可视化Echarts组件开发(28):异形柱图、pictorialBar和dataZoom组件的使用

构建容器 var dom document.getElementById(container);var myChart echarts.init(dom, null, {renderer: canvas,useDirtyRect: false});模拟数据 var dataList [{name: 班级一, value: 120, max: 120, min: 20},{name: 班级二, value: 183, max: 200, min: 20},{name: 班级…

Windows如何删除“$WINDOWS.~BT“文件夹,解决权限不足无法删除

$WINDOWS.~BT是干嘛的 $Windows.BT是升级或者安装Windows操作系统中间过程中产生的临时文件夹&#xff0c;一般用于保存下载后的升级文件&#xff0c;或者安装过程中复制文件时产生的。用于保存Windows安装记录, 包括配置资料, 错误报告等, 如果安装失败便可反馈给微软公司&am…

pytorch学习3(pytorch手写数字识别练习)

网络模型 设置三层网络&#xff0c;一般最后一层激活函数不选择relu 任务步骤 手写数字识别任务共有四个步骤&#xff1a; 1、数据加载--Load Data 2、构建网络--Build Model 3、训练--Train 4、测试--Test实战 1、导入各种需要的包 import torch from torch import nn f…

Matlab图像处理-区域特征

凹凸性 设P是图像子集S中的点&#xff0c;若通过的每条直线只与S相交一次&#xff0c;则称S为发自P的星形&#xff0c;也就是站在P点能看到S的所有点。 满足下列条件之一&#xff0c;称此为凸状的&#xff1a; 1.从S中每点看&#xff0c;S都是星形的&#xff1b; 2.对S中任…

软件设计师笔记系列(四)

&#x1f600;前言 随着技术的快速发展&#xff0c;软件已经成为我们日常生活中不可或缺的一部分。从智能手机应用到大型企业系统&#xff0c;软件都在为我们提供便利、增强效率和创造价值。然而&#xff0c;随之而来的是对软件质量的日益增长的关注。软件的质量不仅关乎其功能…

C语言中的虚拟地址

虚拟地址 虚拟地址空间 对于操作系统而言&#xff0c;每个进程所得到的虚拟地址都在一个独立的固定的范围内&#xff0c;不会超过这个范围&#xff0c;我们把这个范围称为虚拟地址空间。所谓的虚拟地址空间本质就是一个地址范围&#xff0c;表示程序的寻址能力。对于32位系统…

Python 在 JMeter 中如何使用?

要在JMeter中使用Python&#xff0c;需要使用JSR223 Sampler元素来执行Python脚本。使用JSR223 Sampler执行Python脚本时&#xff0c;需要确保已在JMeter中配置了Python解释器&#xff0c;并设置了正确的环境路径。 1、确保JMeter已安装Python解释器&#xff0c;并将解释器的路…

时序预测 | MATLAB实现POA-CNN-BiGRU鹈鹕算法优化卷积双向门控循环单元时间序列预测

时序预测 | MATLAB实现POA-CNN-BiGRU鹈鹕算法优化卷积双向门控循环单元时间序列预测 目录 时序预测 | MATLAB实现POA-CNN-BiGRU鹈鹕算法优化卷积双向门控循环单元时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现POA-CNN-BiGRU鹈鹕算法优化卷积双向…

Jenkins学习笔记6

开发者开发代码一般会使用IDE集成开发工具&#xff08;比如pycharm这种),那么使用pycharm开发的代码能否直接利用自动发布系统发布到业务服务器上呢&#xff1f; 答案是肯定的。 然后进行下测试&#xff1a; 那说明SSH免密是成功的。 将Pycharm修改为原来的界面&#xff0c;然…

抖音短视频矩阵系统搭建

企业在进行短视频矩阵运营时&#xff0c;搭建一个矩阵号是非常必要的。矩阵号可以绑定多个不同平台的账号&#xff0c;批量制作和定时发布短视频&#xff0c;提高企业的曝光量和粉丝互动。但是&#xff0c;如何搭建一个有效的短视频矩阵号呢&#xff1f;以下是几个关键步骤。 一…

STM32 NVIC中断优先级管理通过结构图快速理解

STM32 NVIC中断优先级管理通过结构图快速理解 &#x1f4d1;抢占优先级和响应优先级基本常识 &#x1f33f;抢占优先级的级别高于响应优先级。&#x1f33f;抢占优先级数值编号越小&#xff0c;所代表的优先级就越高&#xff1b;同理&#xff0c;响应优先级也是如此。&#x1…

为什么要选择Spring cloud Sentinel

为什么要选择Spring cloud Sentinel &#x1f34e;对比Hystrix&#x1f342;雪崩问题及解决方案&#x1f342;雪崩问题&#x1f342;.超时处理&#x1f342;仓壁模式&#x1f342;断路器&#x1f342;限流&#x1f342;总结 &#x1f34e;对比Hystrix 在SpringCloud当中支持多…

使用 FHE 实现加密大语言模型

近来&#xff0c;大语言模型 (LLM) 已被证明是提高编程、内容生成、文本分析、网络搜索及远程学习等诸多领域生产力的可靠工具。 大语言模型对用户隐私的影响 尽管 LLM 很有吸引力&#xff0c;但如何保护好 输入给这些模型的用户查询中的隐私 这一问题仍然存在。一方面&#xf…

《从菜鸟到大师之路 Redis 篇》

《从菜鸟到大师之路 Redis 篇》 &#xff08;一&#xff09;&#xff1a;Redis 基础理论与安装配置 Nosql 数据库介绍 是一种 非关系型 数据库服务&#xff0c;它能 解决常规数据库的并发能力 &#xff0c;比如 传统的数据库的IO与性能的瓶颈 &#xff0c;同样它是关系型数据…

Android 11.0 禁止二次展开QuickQSPanel设置下拉QSPanel高度

1.前言 在11.0的系统定制化需求中,在进行systemui的ui定制开发中,有些产品中有需求对原生systemui下拉状态栏中的二次展开QSPanel修改成 一次展开禁止二次展开,所以就需要修改QuickQSpanel的高度,然后在QuickQsPanel做定制,然后禁止二次展开就可以了 如图: 2.禁止二次展开…

32.3D文本旋转动画效果

特效 源码 index.html <!DOCTYPE html> <html> <head> <title>CSS 3D Text Rotation</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body><div class=&quo…

C++实现观察者模式(包含源码)

文章目录 观察者模式一、基本概念二、实现方式三、角色四、过程五、结构图六、构建思路七、完整代码 观察者模式 一、基本概念 观察者模式&#xff08;又被称为模型&#xff08;Model&#xff09;-视图&#xff08;View&#xff09;模式&#xff09;是软件设计模式的一种。在…