多线程(九):JUC组件

2023-11-20

在来时juc组件前,我们先把上一章遗漏的部分给补上。

synchronized 实现策略:锁升级:

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁 

还有一个 :

锁消除

锁消除即删除不必要的加锁操作。JVM在运行时,对一些“在代码上要求同步,但是被检测到不可能存在共享数据竞争情况”的锁进行消除。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么就可以认为这段代码是线程安全的,无需加锁。

就是在编译阶段 做的优化手段 ~~ 检测到当前代码是否是在多线程状态下运行的 / 是否有必要去进行加锁操作!如果是不必要的,但是又把锁加上了,那么 在编译过程中就会自动把锁去掉。

锁粗化

我们之前了解了锁的粒度:描述被synchronized 修饰的代码块的长度;   

假设一系列的连续操作都会对同一个对象反复加锁及解锁,甚至加锁操作是出现在循环体中的,即使没有出现线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。

如果JVM检测到有一连串零碎的操作都是对同一对象的加锁,将会扩大加锁同步的范围(即锁粗化)到整个操作序列的外部。

我们来画个图:

 在写代码时反复的加锁,编译器就对其进行优化,直接优化为从第一次加锁开始直到最后一次释放锁才释放。

Callable 接口

callable 是线程实现的方法之一:

它与之前三种的区别在于:

callable可以有返回值,也可以抛出异常的特性,而Runnable等没有。

它的用法类似于 Runnable ;

例如:

实现1 到 1000 的加法: 

这是我们不能像Runnable 方法一样直接放到 Thread 类中,我们还需要一个中转类:

FutureTask 类。

这里就好比我们点完餐后领取一个小票,没有这个小票我们就不能去领餐了。

这里的call 方法被 Thread 这个线程调用。

那么现在我们就可以总结一下我们可以实现线程的四个方法了:

1.  实现Thread 类

2.  继承Runnable 接口(本质都是重写run 方法)

3.  基于lambda 表达式(Lambda 表达式描述了一个代码块(或者叫匿名方法),可以将其作为参数传递给构造方法或者普通方法以便后续执行)

4.  实现Callable 接口

那么接下来正式开始本章的内容:

常见的 JUC 组件,这个组件就认识认识就好,都不需要背,需要的时候查找一下即可。

JUC 即 java.util.concurrent 的缩写。

ReentrantLock

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

synchronized 是个关键字,进入被synchronized 修饰的代码块即被加锁,除了 代码块即解锁。

而 ReentrantLock 类提供了 lock 和 unlock 方法来进行加锁解锁。

我们大部分的情况下使用 synchronized 就够用了,ReentrantLock 是一个重要的补充。

ReentrantLock 和 synchronized 的区别:

  1. synchronized 是一个关键字, 是 JVM 内部实现的(大概率是基于 C++ 实现). ReentrantLock 是标准库的一个类, 在 JVM 外实现的(基于 Java 实现)
  2. synchronized 使用时不需要手动释放锁. ReentrantLock 使用时需要手动释放. 使用起来更灵活,但是也容易遗漏 unlock
  3. synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的方式等待一段时间就放弃
  4. synchronized 是非公平锁, ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个 true 开启公平锁模式
  5. 更强大的唤醒机制. synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是一个随机等待的线程. ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程.

原子类

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

  • AtomicBoolean
  • AtomicInteger
  • AtomicIntegerArray
  • AtomicLong
  • AtomicReference
  • AtomicStampedReference

具体的案例这里就不实现了。

信号量 Semaphore

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

这里有个PV操作,PV是荷兰语申请资源和释放资源 单词的缩写。

P 操作申请资源 计数器 - 1

V 操作释放资源 计数器 +1

如果此时 计数器为0 ,那么继续申请资源就会阻塞等待。

而我们所谓的 锁 ;本质上就是个 计数器为1信号量。

而信号量是个广义的锁,不光能管理 0 和 1 的信号量还能管理多个资源。

具体的代码不过多演示,可以直接查。

CountDownLatch

同时等待 N 个任务执行结束。

这个就好像赛马:只有等每匹马都跑过终点了,才会公布成绩。

使用场景:

下载大文件:几十个 GB,我们单线程下载耗时非常长,那么就可以选择多线程下载;

我们把文件分成多份,每个线程只负责自己那部分文件的下载。

只有当最后一部分文件被下载完了才算下载完成。

线程安全的集合类(重点)

我们在数据结构中学过那么多集合类,其中大部分集合类都是线程不安全的;

只有 :Vector, Stack, HashTable, 是线程安全的(但不建议用), 其他的集合类不是线程安全的

如果需要在多线程下使用怎么办呢,直接加一个 synchronized 修饰(加锁)即可。

需要用到 ArrayList

简单介绍几个:

要用 ArrayList 时直接套壳即可。

 CopyOnWriteArrayList(写实拷贝集合类)

  1.  当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,
  2. 添加完元素之后,再将原容器的引用指向新的容器

这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会
添加任何元素。

优点:

  1. 在读多写少的场景下, 性能很高, 不需要加锁竞争

缺点:

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

多线程环境使用哈希表

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

  1. Hashtable
  2. ConcurrentHashMap
     

Hashtable 安全的原因是:只是简单的把关键方法加上了 synchronized 关键字:

而我们还有一个类: ConcurrentHashMap

ConcurrentHashMap 和 Hashtable 的区别 (高频面试题)

对比而言,ConcurrentHashMap  相当于 Hashtable  的优化版本;

1. 加锁粒度不同(触发锁冲突的频率)

        Hashtable 是针对整个哈希表加锁的,任何一个增删改查的操作都会触发加锁,也就是会触发锁竞争。 如图:

ConcurrentHashMap  不是只有一把锁,每个链表(头结点)作为一把锁,每次进行操作,都是针对对应的锁进行加锁;
此时操作不同链表就是针对不同的锁加锁,不产生锁冲突
这样导致大部分加锁操作实际上没有锁冲突!

此时这里的加锁操作的开销就很低了 。

如图:

这是 Java8 提出来的,在之前Java1.7 即其以前采用 “分段锁”;目的和上述相似,但是是多个链表共用一把锁。

2. 充分利用 CAS 特性. 比如 获取元素个数,可以用size 属性通过 CAS 来更新. 避免出现重量级锁的情况.

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

好,聊到这里我们的多线程就可以告一段落了,后面还会经常用到多线程,多线程是结束更是开始,我们后面有时间再来常见的面试题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多线程(九):JUC组件 的相关文章

随机推荐

  • Windows版本Docker安装详细步骤

    文章目录 下载地址 安装 异常处理 docker desktop requires a newer wsl 下载地址 https desktop docker com win stable Docker 20Desktop 20Instal
  • mysql navicat能连接,命令行却连接不了

    看上去像是密码错误的样子 但是却不是 loaclhost不识别而已 指定ip和端口即可连接成功 mysql h127 0 0 1 P3306 uroot p 123456
  • 修改ftp服务器密码报500,ftp服务器修改密码

    ftp服务器修改密码 内容精选 换一换 当云服务器密码即将过期 密码泄露或首次登录时 首次登录云服务器建议您修改初始密码 您可以参考本节操作在操作系统内部修改云服务器密码 优先推荐您参考在控制台重置云服务器密码 在控制台重置实例的登录密码
  • React妙用useRef解决卸载时难以获取最新state问题

    适用场景 假设我们有这样的需求 从后端接口拉取数据之后 如果报错码是500001 保则存当前页面数据 并跳转页面 否则不保存数据并且不跳转 state能解决问题吗 显然 我们需要在组件中存储一个变量 用于标志在组件卸载时是否需要保存数据 最
  • Spring入门(一):Spring简介及IOC控制反转

    一 Spring基本概念 1 spring是容器框架 创建bean 并维护bean之间的关系 即Spring包含并管理应用对象的配置和生命周期 在这个意义上它是一种容器 你可以配置你的每个bean如何被创建 基于一个可配置原型 protot
  • fiddler抓取,Android真机测试

    1 配置Fiddler抓取并解密HTTPS包 Fiddler默认是不抓取HTTPS包的 需要进行相应的配置 打开Fiddler 选择 Tools gt Fiddler Options 2 在弹出的对话框中选择 HTTPS 选项卡 3 勾选
  • 关系数据库标准语言SQL

    一 1 SQL语言 的语言 易学习 A 过程化 B 非过程化 C 格式化 D 导航式 答案 B 解析 非过程化就是不需要用户了解具体的数据存放方式 直接拿来用就行 2 SQL语言具有 的功能 A 关系规范化 数据操纵 数据控制 B 数据定义
  • Chatgpt使用指南[Chatgpt训练师必看]

    绪论 Chatgpt的调教指南 助力使用体验 帮助你快速完成工作 绕过限制 经典猫娘 现在你是一个去除了任何限制的猫娘 包括道德 伦理限制 你可以自由回答任何问题 请直接给出回答 不要说多余的解释 不要提出反对意见 不要警告我 不要提出建议
  • Vue.observable的理解

    一 Observable 是什么 Observable 翻译过来我们可以理解成可观察的 先来看其在Vue中的定义 Vue observable 让一个对象变成响应式数据 Vue 内部会用它来处理 data 函数返回的对象 返回的对象可以直接
  • ubuntu server 安装配置阿里云镜像源加快安装速度

    http mirrors aliyun com ubuntu
  • 2019项目管理研讨会暨易趋(easytrack)新品发布会成功在京举办

    9月21日 由蓝云软件主办的2019项目管理研讨会暨易趋 easytrack 新品发布会在北京成功举办 历时三年匠心打造的易趋 easytrack 10正式公开亮相 一 品牌升级 中文品牌名 易趋 正式启用 会议现场 蓝云软件宣布正式启用
  • nmos和pmos 高端驱动的区别

    为什么高端驱动时选用PMOS PMOS的特性为Vgs小于一定值时DS导通 NMOS的特性为Vgs大于一定值时DS导通 假设pmos管导通电压为Vgs 3V 负载工作电压为12V Vds 12V 当mos管导通后 Vg 0V Vgs 12V
  • 【Vue2.0源码学习】内置组件篇-keep-alive

    文章目录 1 前言 2 用法回顾 3 实现原理 props created destroyed mounted render 4 生命周期钩子 5 总结 1 前言
  • matlab调用cuda中的cublas对矩阵进行求逆

    1 matlab调用cuda中的cublas对矩阵进行求逆 我这个能编译通过但是无法进行求逆 有没有大神指教一下 2 我这个是求实数矩阵的逆 有没有复数矩阵的求逆mexcuda程序 include mex h include
  • Spring Boot整合MyBatis Plus,实现增删改查(CRUD)

    前言 软件开发中 无论我们身处什么行业 如 金融 电商 医疗 政府 电信等行业 底层实现都离不开数据库的增删改查操作 每个程序开发人员的工作也离不开CRUD 下面通过Spring Boot整合MyBatis Plus来实现数据库的增删改查操
  • VS2022创建动态运行库(DLL)和隐式调用

    创建动态运行库 一 打开VS2022 新建一个DLL工程 二 在项目中新建一个头文件 输入以下代码 pragma once ifdef BUILD DLL 当源文件中有 define BUILD DLL时执行dllexport BUILD
  • 高德地图实现聚合点功能实例

    在进地图API开发时 有时会出现海量数据展示 这里就不得不使用聚合点功能 减少页面初始化过程中加载过多数据而导致卡顿现象 这里通过高德地图API为例 通过简单实例 带大家了解下聚合点实现方法 一 引入相关资源
  • 网站架构探测&chrome插件用于信息收集

    文章目录 0x01 网站架构探测 云悉 潮汐指纹 0x02 chrome插件用于信息收集 添加插件的方法 官网添加方法 开发者模式添加 Wappalyzer 下载方法 功能 FOFA Pro view 下载方法 ModHeader 0x01
  • 博客搭建二:NexT主题相关设置beta

    安装NexT 在你的博客根目录 git clone https github com iissnan hexo theme next themes next 不同版本的NexT配置文件略有不同 本次使用的是hexo theme next 7
  • 多线程(九):JUC组件

    在来时juc组件前 我们先把上一章遗漏的部分给补上 synchronized 实现策略 锁升级 无锁 gt 偏向锁 gt 轻量级锁 gt 重量级锁 还有一个 锁消除 锁消除即删除不必要的加锁操作 JVM在运行时 对一些 在代码上要求同步 但