并发编程Part 2
1. JMM
问题:请你谈谈你对volatile的理解?
volitile 是 Java 虚拟机提供的一种轻量级的同步机制 ,三大特性:
- 保证可见性
- 不保证原子性
- 禁止指令重排
线程之间如何通信?
- 通信是指线程之间以如何来交换信息。
- 一般线程之间的通信机制有两种:共享内存和消息传递。
- Java的并发采用的是共享内存模型~
什么是JMM?
- Java内存模型即JMM(Java Memeory Model),它并不是真实存在的,而是Java中抽象出来的一个概念,用于多线程场景。
共享内存模型 指的就是 Java内存模型 ( 简称 JMM) { Java多线程内存模型 }- JMM 本身是一种抽象的概念,并不真实存在,它描述的是一组规则或者规范~
- JMM描述了一组规则或规范,定义了一个线程对共享变量写入时对另一个线程是可见的,从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系
- 关于JMM的一些同步的约定:
- 线程解锁前,必须把共享变量立刻刷回主存。
- 线程加锁前,必须读取主存中的最新值到工作内存中!
- 加锁和解锁必须是同一把锁
- 在JMM中把多个线程间通信的共享内存称之为主内存{Main Memory},线程之间的共享变量存储在主内存(main memory)中,而在并发编程中每个线程都维护自己的一个本地内存{工作内存},每个线程都有一个私有的本地内存(Local Memory),其中保存的数据是从主内存中拷贝过来的数据副本,而JMM主要是控制本地内存和主内存之间的数据交互;
本地内存是 JMM 的一个抽象概念,并不真实存在。![]()
JMM内存模型 从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
- 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
- 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
下面通过示意图来说明线程之间的通信:
总结:
- 什么是Java内存模型:Java内存模型简称JMM,定义了一个线程对另一个线程可见。
- 共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。
怎样保证线程B可以同步感知线程A修改了共享变量呢?
- 使用volatile关键字修饰变量
- 使用synchronized修饰修改变量的方法
- wait/notify
- while轮询
JMM内存模型底层八大原子操作:

2. Volatile
volitile 是 Java 虚拟机提供的一种轻量级的同步机制 ,三大特性:
- 保证可见性
- 不保证原子性
- 禁止指令重排,从而避免多线程环境下程序中出现乱序执行的现象
使用volatile可以保证线程间共享变量的可见性,详细的说就是符合以下两个规则:
- 线程对共享变量进行修改之后,要立刻回写到主内存。
- 线程对共享变量读取的时候,要从主内存中读,而不是缓存。
- 对于可见性,Java提供了volatile关键字来保证多线程共享变量可见性和禁止JVM指令重排,volatile提供happens-before{先行发生}的保证,volatile变量可以确保先行关系,即写操作会发生在后续的读操作之前,确保一个线程的修改能对其它线程是可见的。当一个共享变量被volatile关键字修饰时,它会保证修改的值会立即被更新到主内存中,当有其它线程需要读取时,它会从主内存中读取最新的值。
- {volatile关键字为变量的访问提供了一种免锁机制}
- 但是,volatile并不能保证原子性,例如用volatile修饰count变量,那么count++操作就不是原子性的{因为首先count++本身就不是原子性操作}
- 而java.util.concurrent.atomic包下的原子类提供的atomic方法可以保证对其进行原子性操作,比如AtomicInteger类提供的getAndIncrement()方法会原子性的进行增量操作把当前值加1。
2.2 Volatile为什么可以保证可见性和有序性? - Volatile的底层原理
- 由于底层是通过操作系统的内存屏障来实现的,所以Volatile会禁止指令重排,因此同时也就保证了有序性。
2.3 指令重排序与内存屏障
- 并发编程三大特性:原子性,可见性,有序性
- Volatile保证可见性与有序性,但是不能保证原子性,保证原子性需要借助synchronized这样的锁机制
什么是重排序?
- CPU处理器为了提高程序运行效率,可能会对输入代码进行优化,对指令进行重新排序{重排序},它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
- CPU处理器在进行指令重排序时必须要考虑数据之间的依赖性!{举例:一个后续指令要读取一个前面指令写入的数据,那么CPU处理器会确保这两个指令的顺序保持一致。}
指令重排可能带来的问题:
- 指令重排对单线程运行时不会有任何问题,但是多线程就不一定了,可能会导致多线程程序出现内存可见性、有序性、死锁和活锁问题。
- 可见性问题:指令重排可能导致变量的更新在某个线程中不可见,即一个线程对共享变量的更新操作对其它线程来说不可见,这可能会导致数据不一致性问题。
- 有序性问题:指令重排可能会改变操作的执行顺序,从而引起线程之间的有序性问题。在多线程环境下,线程依赖于特定的操作顺序来保证正确的逻辑和数据一致性,指令重排可能会破坏这种有序性。
- 死锁和活锁问题:指令重排可能导致线程在加锁和解锁操作上出现问题{可能导致锁的获取和释放顺序发生变化},进而引发死锁或活锁。
重排序实际执行的指令步骤 :
- 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
- 指令级并行的重排序。现代处理器采用了指令级并行技术(ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
- 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
重排序会遵循as-if-serial和happens-before原则!
2.4 as-if-serial语义
- as-if-serial语义的意思是:不管怎么重排序{编译器和处理器为了提高并行度},线程执行的结果不能被改变{与单线程执行结果一致}
- 为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器做重排序。
2.5 happens-before原则
从JDK1.5开始,Java开始使用新的JSR-133内存模型,提供了happens-before{先行发生}原则来辅助保证程序执行的原子性、可见性以及有序性的问题,它是判断数据是否存在竞争、线程是否安全的依据,它提供了8组规则,通过这些规则可以确定操作之间是否存在happens-before关系。
happens-before原则内容如下{简单说明四点}:
- 程序的顺序性原则:即在一个线程内必须保证语义串行性,也就是说按照代码顺序执行
- 锁规则:解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)操作之前
- volatile规则:volatile变量的写操作,先发生于读操作,这保证了volatile变量的可见性,简单理解就是:volatile变量在每次被线程访问时,都强迫从主内存中读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存,任何时刻,不同的线程总是能看到该变量的最新值。
- 传递性规则:A先于B,B先于C => 那么A必须先于C
总结:as-if-serial语义和happens-before规则这么做的目的,都是为了在不改变程序执行结果的 前提下,尽可能地提高程序执行的并行度。
2.6 内存屏障
- 内存屏障(Memeory Barrier)是一种与CPU相关的特殊指令{CPU指令}。
- 内存屏障可以避免指令重排、保证内存操作的可见性。
- 在Java中,内存屏障的概念被抽象为不同的同步机制,如volatile关键字和synchronized关键字,当使用这些关键字时,编译器和JVM会插入对应的内存屏障指令,以确保内存操作的有序性和可见性。
- 通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化{禁止上面指令和下面指令顺序交换}。
2.7 synchronized 和 volatile 的区别是什么?
- synchronized 表示同一时间只有一个线程可以获取作用对象的锁,去进入同步代码块或者同步方法来执行代码,其他线程会被阻塞。
- volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。
区别:
- volatile 是变量修饰符,volatile关键字只能用于变量;synchronized 关键字可以修饰方法以及代码块。
- volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证对变量的修改操作的可见性和原子性{加锁和解锁}。
- volatile 不会造成线程的阻塞;synchronized 会造成其它线程的阻塞。
- volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。
补充:临界区{有线程安全问题的代码块}
提问:Volatile的内存屏障在哪个地方使最多?
- 在单例模式里面使用最多{懒汉式单例下面的 - 双重检测锁DCL方式实现单例模式}
3. 彻底玩转单例模式
双重检测锁/双重检验锁/双重检查锁(Double Checked Locking) DCL模式的懒汉式单例{线程安全}
package com.gch.dcl;/**DCL双重检验锁机制在多线程环境下实现延迟加载和线程安全的懒汉式单例模式{懒加载}*/
public class SingleInstance {/**1.构造器私有*/private SingleInstance() {}/**2.定义一个私有的用volatile修饰的静态成员变量存储一个对象 => 保证内存可见性防止JVM指令重排*/private volatile static SingleInstance instance;/**3.提供一个方法,对外返回单例对象*/public static SingleInstance getInstance(){/** 双重检验锁的逻辑式通过两次检查instance变量是否为null来实现延迟加载 */// 这是为了防止多个线程同时通过第一次检查,然后同时进入代码块创建实例的情况。// 先判断对象是否已经被实例过{第一次检查}if(instance == null){// 对象没有被实例化过,进入加锁代码{类对象加锁}synchronized(SingleInstance.class){// 第二次检查if(instance == null){// 实例化 => 不是一个原子性操作/*** 1.为对象分配内存空空间* 2.执行构造方法,初始化对象* 3.将对象指向分配的内存空间*/instance = new SingleInstance();}}}return instance;}
}
- 由于JVM具有指令重排的特性,执行顺序有可能变成1 -> 3 -> 2。
- 指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。
- 例如:线程T1执行了1和3,此时线程T2调用了getInstance()方法后方法instance不为空,因此直接返回instance,但此时instance变量还未被初始化。
- 因此instance变量使用volatile关键字修饰很有必要,可以禁止JVM的指令重排,保证在多线程环境下也能正常运行。
通过反射暴力破解单例模式:
package com.gch.dcl;import java.lang.reflect.Constructor;public class Main {public static void main(String[] args) throws Exception {// 1.获取Class字节码文件的对象Class<SingleInstance> clazz = SingleInstance.class;// 2.获取到空参的构造方法Constructor<SingleInstance> constructor = clazz.getDeclaredConstructor();// 3.暴力反射,表示临时取消访问权限,表示权限被打开constructor.setAccessible(true);// 4.利用获取到的构造方法去创建对象SingleInstance instance1 = constructor.newInstance();SingleInstance instance2 = constructor.newInstance();System.out.println(instance1);System.out.println(instance2);System.out.println(instance1 == instance2);}
}
使用枚举实现单例 - 通过枚举防止反射破解创建实例对象:
不能使用反射破坏枚举
枚举类的实例是在类加载时被初始化的,而且枚举类不支持反射{创建实例},因此使用枚举类实现的单例模式是天然防止反射破解的。
使用枚举实现单例模式的优点:
- 线程安全:枚举的实例在任何情况下都是唯一的,不需要担心多线程环境下的竞争条件。
- 防止反射:枚举类不支持反射,可以防止通过反射破解单例模式。
package com.gch.dcl;import java.lang.reflect.Constructor;/**不能使用反射破坏枚举,枚举不支持反射*/
public enum SingleInstanceEnum {INSTANCE;
}class Test{public static void main(String[] args) throws Exception {SingleInstanceEnum instance1 = SingleInstanceEnum.INSTANCE;SingleInstanceEnum instance2 = SingleInstanceEnum.INSTANCE;SingleInstanceEnum instance3 = SingleInstanceEnum.INSTANCE;System.out.println(instance1);System.out.println(instance2);System.out.println(instance3);/**尝试使用反射破坏枚举 => 运行时报错java.lang.NoSuchMethodException*/// 1.获取Class字节码文件的对象Class<SingleInstanceEnum> clazz = SingleInstanceEnum.class;// 2.获取到空参的构造方法Constructor<SingleInstanceEnum> constructor = clazz.getDeclaredConstructor();// 3.暴力反射,表示临时取消访问权限,表示权限被打开constructor.setAccessible(true);// 4.利用获取到的构造方法去创建对象SingleInstanceEnum instance4 = constructor.newInstance();SingleInstanceEnum instance5 = constructor.newInstance();System.out.println(instance4);System.out.println(instance5);}
}

4. 深入理解CAS
5. 原子引用
6. 各种锁的理解
6.1 公平锁、非公平锁
6.2 可重入锁
6.3 自旋锁
6.4 死锁
相关文章:

并发编程Part 2
1. JMM 问题:请你谈谈你对volatile的理解? volitile 是 Java 虚拟机提供的一种轻量级的同步机制 ,三大特性: 保证可见性 不保证原子性 禁止指令重排 线程之间如何通信? 通信是指线程之间以如何来交换信息。一般线程之间的通信…...
springboot异步多线程的实现
1、配置线程池相关参数 package com.xxx.test.config;import lombok.extern.slf4j.Slf4j; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation…...

测试相关基础概念与常见开发模型
目录 1. 什么是需求 1.1 需求的定义 1.2 为什么有需求?为什么需求对软件测试人员如此重要? 1.3 测试人员眼里的需求(重要) 1.4 如何深入了解需求 2. 测试用例 2.1 什么是测试用例 2.2 为什么有测试用例 3. 什么是BUG 3.1 BUG定义 3.2 如何描述一个BUG 3.3 如何定义bug的级别 …...

MySQL安装详细教程!!!
安装之前,先卸载你之前安装过的数据库程序,否则会造成端口号占用的情况。 1.首先下载MySQL:MySQL :: Download MySQL Community Server(下载路径) 2.下载版本不一样,安装方法略有不同;(版本5的安装基本一致,…...

前端下载文化部几种方法(excel,zip,html,markdown、图片等等)和导出 zip 压缩包
文章目录 1、location.href2、location.href3、a标签4、请求后端的方式5、文件下载的方式6、Blob和Base647、下载附件方法(excel,zip,html,markdown)8、封装下载函数9、导出 zip 压缩包相关方法(流方式) 总结 1、location.href //get请求 window.location.href url;2、locati…...
铠甲网络面试(部分)
如何用Redis实现分布式锁的?如果设置的超时时间到了,但占有锁的任务还未完成,怎么办?答案:定时任务进行检测与续约,具体参考 本博----《专题三分布式系统》之《第三章 集中式缓存Redis》之 《第三节 Redis底…...

elasticsearch 将时间类型为时间戳保存格式的时间字段格式化返回
dsl查询用法如下: GET /your_index/_search {"_source": {"includes": ["timestamp", // Include the timestamp field in the search results// Other fields you want to include],"excludes": []},"query": …...
淘宝商品列表怎么通过接口形式导出?
淘宝是目前国内最大的电商平台之一,拥有海量的商品资源。但是,有时候我们需要对淘宝商品进行批量操作,比如进行价格比较、数据分析等等。这时候,我们就需要将淘宝商品列表导出。那么,淘宝商品列表怎么导出呢?又可以按…...

TWS真无线蓝牙耳机哪家好?六款口碑好的TWS真无线蓝牙耳机分享
为了帮助大家在这个充满选择的世界中找到最理想的蓝牙耳机,我们特别为您精心挑选了几款备受赞誉的产品,它们在音质、舒适度、功能和性价比等方面都有出色的表现。在本文中,我们将深入探讨这些蓝牙耳机的特点和优势,帮助您更好地了…...

解决Win11右键菜单问题
✅作者简介:大家好,我是Cisyam,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Cisyam-Shark的博客 💞当前专栏: 程序日常 ✨特色专栏&…...

开源元数据管理平台Datahub最新版本0.10.5——安装部署手册(附离线安装包)
大家好,我是独孤风。 开源元数据管理平台Datahub近期得到了飞速的发展。已经更新到了0.10.5的版本,来咨询我的小伙伴也越来越多,特别是安装过程有很多问题。本文经过和群里大伙伴的共同讨论,总结出安装部署Datahub最新版本的部署手…...

归并排序——“数据结构与算法”
各位CSDN的uu们好呀,今天,小雅兰的内容仍然是数据结构与算法专栏的排序呀,下面,让我们进入归并排序的世界吧!!! 归并排序 归并排序(MERGE-SORT)是建立在归并操作上的一种…...

C语言笔试题训练【第一天】
目录 第一题 第二题 第三题 第四题 第五题 大家好,我是纪宁。 从今天开始博主会日更一些经典的C语言笔试题,持续20天左右。题目类型为5道选择题加2道编程题,希望能和大家一起进步。 第一题 1.读程序,下面程序正确的输出是&…...
计算语言模型计算每秒钟生成的token数量it/s
在 main() 函数的stream循环中,我们可以计算每秒钟生成的token数量,然后输出 it/s。在流式生成过程中,我们可以使用Python的time模块来计算速度。在测试时,生成速度会受到多个因素的影响,包括设备性能、模型大小、输入…...
Clickhouse调研
1、独立组件个数(按进程) 默认情况下是1个;如果需要使用副本机制,需要依赖zookeeper;如果需要监控功能,还得依赖第三方监控系统。 2、单机部署 很好的支持单机运行,并且单机情况下查询入库性能不错(通过其提供的示例数据进行体验)。 3、窗口函数 Clickhouse没有显示…...

02.Redis实现添加缓存功能
学习目标: 提示:学习如何利用Redis实现添加缓存功能 学习产出: 流程图 1. 准备pom环境 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId&g…...

【1.2】Java微服务:SpringCloud概论
✅作者简介:大家好,我是 Meteors., 向往着更加简洁高效的代码写法与编程方式,持续分享Java技术内容。 🍎个人主页:Meteors.的博客 💞当前专栏: 微服务 ✨特色专栏: 知识分享 &#x…...

右键文件夹 ------- 打开 vscode的方法
1、右键vscode点击属性 2、这是地址栏,一会复制即可 3、新建一个txt文件,将这个复制进去 Windows Registry Editor Version 5.00[HKEY_CLASSES_ROOT\*\shell\VSCode] "Open with Code" "Icon""D:\\Microsoft VS Code\\Code.exe"[HKE…...

小程序原生实现左右锚点联动
效果 wxml <view classbox><scroll-view scroll-y scroll-with-animation style"width:25%"><view classnav><view wx:for"{{navList}}" wx:keyindex class"title {{index active ?select:}}"data-index{{index}} bin…...

STM32 低功耗-睡眠模式
STM32 睡眠模式 文章目录 STM32 睡眠模式第1章 低功耗模式简介第2章 睡眠模式简介2.1 进入睡眠模式2.1 退出睡眠模式 第3章 睡眠模式代码示例总结 第1章 低功耗模式简介 在 STM32 的正常工作中,具有四种工作模式:运行、睡眠、停止和待机模式。 在系统或…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...

华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...

力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...