【JavaEE初阶】第二节.多线程( 进阶篇 ) 锁的优化、JUC的常用类、线程安全的集合类
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、synchronized的优化操作
- 1.1 锁膨胀/锁升级
- 1.2 锁消除
- 1.3 锁粗化
- 二、JUC
- 2.1 Callable接口
- 2.2 ReentrantLock类(可重入锁)
- 2.3 原子类
- 2.4 Semaphore类(信号量)
- 2.5 CountDownLatch类
- 三、线程安全的集合类
- 3.1 多线程使用顺序表
- 3.2 多线程环境使用队列
- 3.3 多线程环境使用哈希表
前言
这篇博客主要介绍 synchronized 的底层工作原理,包括:锁膨胀/锁升级、锁消除、锁粗化 ;并且介绍了 关于JUC的详细知识点 ;以及一些线程安全的集合类 ;
一、synchronized的优化操作
由上一篇博客,我们可以知道,synchronized 使用的锁策略 有以下特点:
- 既是悲观锁,也是乐观锁(自适应)
- 既是轻量级锁,也是重量级锁(自适应)
- 轻量级锁部分基于自旋锁实现,重量级锁部分基于挂起等待锁来实现
- 不是读写锁
- 是非公平锁
- 是可重入锁
1.1 锁膨胀/锁升级
现在,我们需要知道 synchronized 是怎样 自适应的?(这个过程,又叫做 锁膨胀/锁升级)
synchronized 在加锁的时候要经历几个阶段:
- 当线程没有加锁的时候,这个阶段叫做 无锁;
- 当线程刚开始加锁,并没有产生竞争的时候,这个阶段叫做 偏向锁;
- 当线程进行加锁,并且已经产生锁竞争了,这个阶段叫做 轻量级锁;
- 如果此时锁竞争的更激烈了,这个阶段叫做 重量级锁;
图示解析:
当然,这几个阶段,并不是说 每一次加锁都会一直加到 最后,可能会到某一阶段之后,就会解锁了;不会每一个阶段都会经历,但会经历到某一部分 ;
像上面的,锁的状态不断的升级,锁的竞争也逐渐加剧 的情况,就叫做 锁膨胀/锁升级;
这里就先重点介绍一下 偏向锁;
所谓偏向锁,并不是 "真正加锁",而是只使用一个标记表示 "这个锁是我的了",在遇到其他线程来竞争锁之前,会一直保持着这个状态;
直到真的有线程来竞争锁了,此时才会真正的加锁;
这个过程类似于 单例模式中的 "懒汉模式" —— 在必要的时候进行加锁,从而会节省一定的开销;
因为如果没有额外的线程 来参与锁竞争的话,那么就会一直处于偏向锁的状态,也就省去了加锁和解锁的开销了;
1.2 锁消除
synchronized 除了锁升级,还有其他的优化操作,比如说:锁消除
所谓锁消除,即 编译器自动锁定,如果认为这个代码没必要加锁,就不加了!!!
代码举例:
StringBuffer sb = new StringBuffer(); sb.append("a"); sb.append("b"); sb.append("c"); sb.append("d"); //就比如说,StringBuffer 是线程安全的,它的 append 等方法,都是带有synchronized, //如果上述代码 都只是在同一个线程里面执行的,那么此时就没有必要加锁了 //此时,JVM 就自动悄悄的把锁给去掉了(这也属于编译器优化的一个方面)
但是,锁消除的操作 不是所有的情况下都会触发,实际上,大部分情况下不能被触发;
而 向偏向锁 则是每一次加锁,都会进入到偏向锁的状态;
1.3 锁粗化
锁粗化,也是 synchronized 的一种优化方式 ;
在这之前,我们需要知道 锁的粒度 这个概念;
所谓 锁的粒度,指的是 synchronized 包含的代码范围是大还是小;
范围越大,粒度越粗 ;范围越小,粒度越细!!!
锁的粒度越细,能够更好的提高线程的并发,但是也会增加 "加锁解锁" 的次数 ;
锁粗化,也是类似的情况:它把一段代码中,频繁的加锁解锁,"粗化" 成一次加锁解锁了 ;
图示分析:
毕竟,加锁解锁 也是需要开销的嘛
二、JUC
所谓 JUC,实际上指的是:java.util.concurrent,它是一个包;
在这个包里面 存放了很多和多线程开发 的相关的类、接口;
2.1 Callable接口
Callable接口 和 前面的Runnable 非常相似,都是可以在创建线程的时候,来指定一个 "具体的任务" ;
区别是:Callable 指定的任务是带返回值的,Runnable 指定的任务是不带返回值的;
Callable 提供的 call方法,可以方便的获取到代码的执行结果 ;
如:创建线程计算 1+2+3+ ... +1000,使用Callable版本 ;
代码示例:
package thread;import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask;public class Demo29 {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 = 1; i <= 1000; i++) {sum += i;}return sum;}};//套上一层,目的是为了获取到后续的结果FutureTask<Integer> task = new FutureTask<>(callable);Thread t = new Thread(task);t.start();//在 线程t 执行结果之前,get会阻塞,//直到 线程t 执行完了,结果算好了,get 才能返回,返回值就是 call方法 return 的内容//获取执行结果System.out.println(task.get());} }
运行结果:
需要重点理解的是 下面一部分代码:
//给 计算结果 一个小票 FutureTask<Integer> task = new FutureTask<>(callable);
在线程需要计算结果的情况下,需要非常明确的知道 这个结果是谁来算的,不能像以前一样,直接把 callable 加到 Thread 的构造方法之中(之前使用 Runnable 的时候,不需要知道一个明确的结果):
就类似于,去餐馆吃饭的时候,人多的时候,老板会给一个小票,到时候就可以凭着小票来取餐,不至于搞乱了 ;
2.2 ReentrantLock类(可重入锁)
我们已经知道,synchronized 已经是一个可重入锁了,但是 为啥还要再搞一个 ReentrantLock 呢?
实际上,ReentrantLock 还是和 synchronized 之间有很大的差别的:
1.synchronized 是一个单纯的关键字,以代码块为单位进行加锁解锁;ReentrantLock 则是一个类,提供 lock方法 加锁,unlock方法 解锁;
2.ReentrantLock 在构造实例的时候,可以指定一个 fair参数 来决定锁对象是 公平锁还是非公平锁;synchronized 加的锁只能是非公平锁,不能指定为公平锁
3.ReentrantLock 还提供了一个特殊的加锁操作 —— tryLock() 方法;
4.ReentrantLock 提供了更加强大的 等待/唤醒 机制;
说明:大部分情况下,使用锁还是以 synchronized 为主,特殊场景下,才使用 ReentrantLock
2.3 原子类
原子类内部用的是 CAS 实现的,所以性能要比加锁实现 i++ 高很多 ;
原子类有以下几个:
- AtomicBoolean
- AtomicInteger
- AtomicInterArray
- AtomicLong
- AtomicReference
- AtomicStampedReference
举例说明:
以 AtomicInteger 为例,常见方法有:
addAndGet(int delta); <=> i += delta; decrementAndGet(); <=> --i; getAndDecrement(); <=> i--; incrementAndGet(); <=> ++i; getAndIncrement(); <=> i++;
2.4 Semaphore类(信号量)
所谓信号量,可以理解成 计数器,描述了可用资源的个数;
举例说明:
比如说,停车场的入口处,会有牌子(上面显示:当前有车位 N 个);
当有车开进去以后,N 就会减去一个;当有车从出口开出去以后,N 就会加上一个 ;
这个 N 就叫做 信号量 ;
如果当前 N 已经是 0 了,如果还有车想进来,那就进不去了,新来的车只能阻塞等待,直到有车给开出去 ;
如上所示:
把车开进去,称之为 申请一个可用资源,信号量 -= 1,称为 P 操作 ;
把车开出来,称之为 释放一个可用资源,信号量 +=1,称为 V 操作 ;
其实,信号量 可以把它视为一个 更广义的锁,当信号量的取值是 0~1 的时候,就退化成了一个普通的锁 ;
锁 相当于是一个可用资源是 1 的信号量,只要加锁成功,其他的线程就获取不了 ;
但是,信号量不一样,只要可用资源还有,就可以不断的进行 P 操作 ;
说明:我们需要知道,上面的信号量 +=1,-=1 都是原子的;
代码演示:
package thread;import java.util.concurrent.Semaphore;public class Demo31 {public static void main(String[] args) throws InterruptedException {//构造方法传入有效资源的个数是 3 个Semaphore semaphore = new Semaphore(3);//P操作 申请资源,使用 acquire方法//每一次使用一个资源,信号量 -1semaphore.acquire();System.out.println("申请资源");semaphore.acquire();System.out.println("申请资源");semaphore.acquire();System.out.println("申请资源");semaphore.acquire();System.out.println("申请资源");} }
运行结果:
有效资源是 3 份,即使 想要申请4份资源,但是在运行结果中显示,最终只是申请了 3 份资源 ;
只有前面的线程把资源释放了,才可以使用 ;
代码示例:
package thread;import java.util.concurrent.Semaphore;public class Demo31 {public static void main(String[] args) throws InterruptedException {//构造方法传入有效资源的个数是 3 个Semaphore semaphore = new Semaphore(3);//P操作 申请资源,使用 acquire方法//每一次使用一个资源,信号量 -1semaphore.acquire();System.out.println("申请资源");semaphore.acquire();System.out.println("申请资源");semaphore.acquire();System.out.println("申请资源");//V操作 释放资源,使用 release 方法//此时,信号量 = 0,线程进入阻塞,只有释放资源,线程才可以继续运行semaphore.release();System.out.println("释放资源成功");semaphore.acquire();System.out.println("申请资源成功");} }
运行结果:
2.5 CountDownLatch类
CountDownLatch类,相当于 在一个大的任务被拆分成若干个子任务的时候,用这个来衡量 什么时候这些子任务都执行结束 ;
举个例子:
此时在某地正在进行一场跑步比赛,只有当所以选手都到达终点的时候,裁判才可以吹哨结束比赛 ;
CountDownLatch 描述的就是啥时候所有选手都到达终点 ;
代码示例:
package thread;import java.util.concurrent.CountDownLatch;public class Demo32 {public static void main(String[] args) throws InterruptedException {//模拟跑步比赛//构造方法中设定有 10 个选手参赛CountDownLatch latch = new CountDownLatch(10);for (int i = 0; i < 10; i++) {Thread t = new Thread(() -> {try {Thread.sleep(3000);System.out.println(Thread.currentThread().getName()+" 到达终点");//countDown 相当于 "撞线"latch.countDown();} catch (InterruptedException e) {e.printStackTrace();}});t.start();}//await 再等待所有的线程都 "撞线"//换句话说,调用 countDown 的次数达到初始化的时候 设定的值,//await 就返回,否则 await 就阻塞等待latch.await();System.out.println("比赛结束");} }
运行结果:
三、线程安全的集合类
原来的集合类,大部分都是 线程不安全的 ;
换句话来说,大部分集合类在多线程环境下使用 都是有问题的 ;
总结:
ArrayList、LinkedList、TreeSet、TreeMap、HashSet、HashMap、Queue 是线程不安全的;
Vector、Stack、HashTable 是线程安全的 ;
3.1 多线程使用顺序表
(一)自己使用加锁操作
(二)使用 Collections.synchronizedList (new ArrayList);
synchronizedList 是标准库提供的一个基于 synchronized 进行线程同步的 List ;
synchronizedList 的关键操作上都带有 synchronized ;
(三)使用 CopyOnWriteArrayList;
如果出现修改操作,就把 Ar rayList 进行复制 ;
先拷贝一份数据(创建一份副本),再接着新线程修改副本,修改完成后,再用副本替换原有的数据 ;
这就叫做 写实拷贝 ;
不过这种行为的 拷贝成本可能会很高,所以 一般元素个数多的时候会用到 ;
3.2 多线程环境使用队列
多线程环境下常常使用以下阻塞队列:
- ArrayBlockingQueue 基于数组实现的阻塞队列;
- LinkedBlockingQueue 基于链表实现的阻塞队列;
- PriorityBlockingQueue 基于堆实现的带优先级的阻塞队列;
- TransferQueue 最多只包含一个元素的阻塞队列;
3.3 多线程环境使用哈希表
HashMap本身不是线程安全的,在多线程环境下可以使用:
- HashTable
- ConcurrentHashMap
HashTable
其中,HashTable 直接搞了一个大锁,这就导致了 在多个线程去修改上面的元素 的时候,都需要去进行加锁控制,就会有锁冲突,比较低效:
对于两个不同的哈希桶上的元素,不牵扯修改同一个变量,就不会发生线程安全问题(此时加锁的话,意义不大);
但是,如果两个修改涉及到同一个哈希桶上,就会有线程安全问题 ;
于是,HashTable 就会显得低效 ;
ConcurrentHashMap
相比之下,ConcurrentHashMap类 做出了重大的改进 —— 把锁的粒度细化了!!!
接下来介绍的 ConcurrentHashMap类 基于 java 1.8 的 ;
该类是基于哈希表中的每一个链表对象进行加锁,线程需要对哪个链表对象进行操作,就在哪里加锁;
由于哈希表中链表数量很多,链表对象的元素个数较少,可以有效地降低锁竞争的概率 ;
ConcurrentHashMap 优化特点:
[ 最重要 ] 把锁的粒度细化,降低锁冲突的概率;
有一个激进的操作:读没加锁,写才加锁;
更充分的使用了 CAS 特性,达到一个更高效的操作(如 维护 size 的时候);
针对扩容场景进行了优化(化整为零,不会一口气的完成扩容;而是 每次基本操作,都扩容一点点,逐渐完成整个扩容 —— 不会特别卡);
总结
今天的关于多线程( 进阶篇 ) 锁的优化、JUC的常用类、线程安全的集合类就讲到这里,我们下一节内容再见!!!!
相关文章:

【JavaEE初阶】第二节.多线程( 进阶篇 ) 锁的优化、JUC的常用类、线程安全的集合类
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、synchronized的优化操作 1.1 锁膨胀/锁升级 1.2 锁消除 1.3 锁粗化二、JUC 2.1 Callable接口 2.2 ReentrantLock类&…...

大数据核心技术是什么
大数据的核心层:数据采集层、数据存储与分析层、数据共享层、数据应用层,可能叫法有所不同本质上的角色都大同小异。 大数据的核心技术都包括什么? 1、数据采集 数据采集的任务就是把数据从各种数据源中采集和存储到数据存储上,…...

「TCG 规范解读」初识 TPM 2.0 库续一
可信计算组织(Ttrusted Computing Group,TCG)是一个非盈利的工业标准组织,它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立,并采纳了由可信计算平台联盟(the Trusted Computing Platform Alli…...

task与function
task和function主要是有助于代码的可重用性,都可以在module-endmodule之外声明。 1.function 1.1.function逻辑的综合 function:一个只有1个wire型输出值、全是组合逻辑的函数,且函数名即输出信号名,小括号中按顺序例化输入信号。…...

Android 基础知识4-3.1 TextView(文本框)详解
一、前言 TextView就是一个显示文本标签的控件,就是用来显示文本。可以在代码或者 XML中设置字体,字体大小,字体颜色 ,字体样式 (加粗级斜体),文字截断(比如:只显示10个字…...

点击化学 PEG 试剂1858242-47-3,Propargyl丙炔基-PEG1-乙酸活性酯
Propargyl-PEG1-Acetic acid-NHS ester,丙炔基-聚乙二醇-乙酸琥珀酰亚胺酯,丙炔基-PEG1-乙酸活性酯,丙炔基-PEG1-乙酸-NHS 酯产品规格:1.CAS号:1858242-47-32.分子式:C9H9NO53.分子量:211.174.包…...

正则表达式是如何运作的?
在日常的开发工作当中,我们必不可免的会碰到需要使用正则的情况。 正则在很多时候通过不同的组合方式最后都可以达到既定的目标结果。比如我们有一个需要匹配的字符串: hello,我们可以通过 / .</p>/ 以及 / .?</p>/ 来匹配&…...

JVM参数GC线程数ParallelGCThreads设置
1. ParallelGCThreads参数含义JVM垃圾回收(GC)算法的两个优化标的:吞吐量和停顿时长。JVM会使用特定的GC收集线程,当GC开始的时候,GC线程会和业务线程抢占CPU时间,吞吐量定义为CPU用于业务线程的时间与CPU总消耗时间的比值。为了承…...

java 线程的那些事
什么是进程: 你把它理解成一个软件 什么是线程: 你把它理解成软件里面的一个功能,做的事情 什么是多线程: 你把它理解成 软件里面的某一个功能,原先是一个人累死累活的在那里完成,现在好了,多…...

如何利用 Python 进行客户分群分析(附源码)
每个电子商务数据分析师必须掌握的一项数据聚类技能 如果你是一名在电子商务公司工作的数据分析师,从客户数据中挖掘潜在价值,来提高客户留存率很可能就是你的工作任务之一。 然而,客户数据是巨大的,每个客户的行为都不一样。20…...

D1s RDC2022纪念版开发板开箱评测及点屏教程
作者new_bee 本文转自:https://bbs.aw-ol.com/topic/3005/ 目录 芯片介绍开发板介绍RT-Smart用户态系统编译使用感想引用 1. 芯片介绍 RISC-V架构由于其精简和开源的特性,得到业界的认可,近几年可谓相当热门。操作系统方面有RT-Thread&am…...

了解一下TCP/IP协议族
在《简单说说OSI网络七层模型》中讲到,目前实际使用的网络模型是 TCP/IP 模型,它对 OSI 模型进行了简化,只包含了四层,从上到下分别是应用层、传输层、网络层和链路层(网络接口层),每一层都包含…...

【第十九部分】存储过程与存储函数
【第十九部分】存储过程与存储函数 文章目录【第十九部分】存储过程与存储函数19. 存储过程与存储函数19.1 存储过程19.2 创建、调用存储过程19.2.1 不带参数19.2.2 IN 类型19.2.3 OUT类型19.2.4 IN和OUT类型同时使用19.2.5 INOUT类型19.3 存储函数19.4 创建、调用存储函数19.5…...

字节序
字节序 字节序:字节在内存中存储的顺序。 小端字节序:数据的高位字节存储在内存的高位地址,低位字节存储在内存的低位地址 大端字节序:数据的低位字节存储在内存的高位地址,高位字节存储在内存的低位地址 bit ( 比特…...

PDF文件怎么转图片格式?转换有技巧
PDF文件有时为了更美观或者更直观的展现出效果,我们会把它转成图片格式,这样不论是归档总结还是存储起来都会更为高效。有没有合适的转换方法呢?这就来给你们罗列几种我个人用过体验还算不错的方式,大家可以拿来参考一下哈。1.用电…...

筑基七层 —— 数据在内存中的存储?拿来吧你
目录 零:移步 一.修炼必备 二.问题思考 三.整型在内存中的存储 三.大端字节序和小端字节序 四.浮点数在内存中的存储 零:移步 CSDN由于我的排版不怎么好看,我的有道云笔记相当的美观,请移步至有道云笔记 一.修炼必备 1.入门…...

Typecho COS插件实现网站静态资源存储到COS,降低本地存储负载
Typecho 简介Typecho 是一个简单、强大的轻量级开源博客平台,用于建立个人独立博客。它具有高效的性能,支持多种文件格式,并具有对设备的响应式适配功能。Typecho 相对于其他 CMS 还有一些特殊优势:包括可扩展性、不同数据库之间的…...

2月23号作业
题目:题目一:通过操作Cortex-A7核,串口输入相应的命令,控制LED灯进行工作--->上传CSDN 1.例如在串口输入led1on,开饭led1灯点亮 2.例如在串口输入led1off,开饭led1灯熄灭 3.例如在串口输入led2on,开饭led2灯点亮 4.例如在串口输…...

因果推断方法(一)合成控制
知道的跳过下面的简单介绍: 就是比如广告主投放了10w元,那么他的收益怎么算?哪些订单就是广告带来的,哪些是不放广告也会购买? 合成控制法是目前我实际应用发现最好用的。置信度高,且容易理解。 简单讲下思…...

数据结构第12周 :( 有向无环图的拓扑排序 + 拓扑排序和关键路径 + 确定比赛名次 + 割点 )
目录有向无环图的拓扑排序拓扑排序和关键路径确定比赛名次割点有向无环图的拓扑排序 【问题描述】 由某个集合上的一个偏序得到该集合上的一个全序,这个操作被称为拓扑排序。偏序和全序的定义分别如下:若集合X上的关系R是自反的、反对称的和传递的&…...

Linux安装docker(无网)
1. 下载Docker安装包 下载地址:https://download.docker.com/linux/static/stable/x86_64/ 如果服务器可以联网可以通过wget下载安装包 wget https://download.docker.com/linux/static/stable/x86_64/docker-18.06.3-ce.tgz2. 解压安装 tar -zxvf docker-18.06…...

解决JNI操作内核节点出现写操作失败的问题
Android 9.0下,因为采取了SEAndroid/SElinux的安全机制,即使拥有root权限,或者对某内核节点设置为777的权限,仍然无法在JNI层访问。 本文将以用户自定义的内核节点/dev/wf_bt为例,手把手教会读者如何在JNI层获得对该节…...

纵然是在产业互联网的时代业已来临的大背景下,人们对于它的认识依然是短浅的
纵然是在产业互联网的时代业已来临的大背景下,人们对于它的认识依然是短浅的。这样一种认识的最为直接的结果,便是我们看到了各式各样的产业互联网平台的出现。如果一定要找到这些互联网平台的特点的话,以产业端为出发点,无疑是它…...

干翻 nio ,王炸 io_uring 来了 !!(图解+史上最全)
大趋势:全链路异步化,性能提升10倍 随着业务的发展,微服务应用的流量越来越大,使用到的资源也越来越多。 在微服务架构下,大量的应用都是 SpringCloud 分布式架构,这种架构总体上是全链路同步模式。 全链…...

ur3+robotiq ft sensor+robotiq 2f 140+realsense d435i配置rviz,gazebo仿真环境
ur3robotiq ft sensorrobotiq 2f 140realsense d435i配置rviz,gazebo仿真环境 搭建环境: ubuntu: 20.04 ros: Nonetic sensor: robotiq_ft300 gripper: robotiq_2f_140_gripper UR: UR3 reasense: D435i 通过下面几篇博客配置好了ur3、力传…...

ASP.NET Core MVC 项目 AOP之Authorization
目录 一:说明 二:传统鉴权授权的基本配置 三 :角色配置说明 四:策略鉴权授权 五:策略鉴权授权Requirement扩展 总结 一:说明 鉴权:是指验证你是否登录,你登录后的身份是什么。…...

智能新冠疫苗接种助手管理系统
项目背景介绍 近几年来,网络事业,特别是Internet发展速度之快是任何人都始料不及的。目前,由于Internet表现出来的便捷,快速等诸多优势,已经使它成为社会各行各业,甚至是平民大众工作,生活不可缺少的一个重…...

Python+Selenium4元素交互1_web自动化(5)
目录 0. 上节回顾 1. 内置的等待条件 2. 元素属性 1. Python对象属性 2. HTML元素属性 3. 元素的交互 1. 输入框 2. 按钮 3. 单选框和复选框 0. 上节回顾 DEBUG的方式:JS断点 Python断点编程语言提供的等待方式:sleepselenium提供的等待方式&…...

2023双非计算机硕士应战秋招算法岗之深度学习基础知识
word版资料自取链接: 链接:https://pan.baidu.com/s/1H5ZMcUq-V7fxFxb5ObiktQ 提取码:kadm 卷积层 全连接神经网络需要非常多的计算资源才能支撑它来做反向传播和前向传播,所以说全连接神经网络可以存储非常多的参数,…...

Python opencv进行矩形识别
Python opencv进行矩形识别 图像识别中,圆形和矩形识别是最常用的两种,上一篇讲解了圆形识别,本例讲解矩形识别,最后的结果是可以识别出圆心,4个顶点,如下图: 左边是原始图像,右边是识别结果,在我i5 10400的CPU上,执行时间不到8ms。 识别出结果后,计算任意3个顶点…...