- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
王有志 ,一个分享硬核Java技术的互金摸鱼侠 加入Java人的提桶跑路群: 共同富裕的Java人 。
今天是《面霸的自我修养》的第二弹,内容是 Java 并发编程中关于 Java 内存模型(Java Memory Model)和锁的基础理论 相关的问题。这两块内容的八股文倒是不多,但是难度较大,接下来我们就一起一探究竟吧.
以下是“叠甲”声明 :
关于 Java 内存模型的内容可以说是八股文中最晦涩难懂的部分之一了, 《JSR-133 Java Memory Model and Thread Specification》中是这么介绍这部分内容的:
The discussion and development of this specification has been unusually detailed and technical, involving insights and advances in a number of academic topics. 。
不过还好,面试中通常不会过分深入的考察 Java 内存模型的部分.
难易程度 :❤❤❤❤❤ 重要程度 :❤❤❤❤❤ 面试公司 :美团,爱奇艺,阿里巴巴 。
Java 内存模型(Java Memory Model,JMM)是 Java 语言规范中的一套规则,它描述了多线程环境下的线程与内存(主内存和高速缓存)的交互方式,以保证可见性,有序性和原子性,同时它屏蔽了硬件与操作系统的底层差异,使得 Java 程序在所有平台下的内存访问效果一致.
我们知道,CPU 的运算速度是远高于内存读写速度的,为了减少速度间的差异,CPU 为每个核心引入了高速缓存(通常分为 L1,L2 和 L3)。多线程的程序中,线程可能会运行在不同的核心上,这时它们使用自己缓存中从主内存拷贝的数据副本,假设每个 CPU 只有一个高速缓存,画一个简易的模型:
如果线程 T1 和线程 T2 分别从主内存中读取同一个数据的到自己的高速缓存中进行操作,如果线程 T1 是先于线程 T2 发生的,那么此时线程 T2 无法感知到线程 T1 对缓存中数据做出的修改,导致可线程间的可见性问题.
Tips :虽然硬件层面引入了缓存一致性协议,但仍旧存在可见性问题,另外不同的 CPU 架构对缓存一致性协议的实现不同导致出现的问题也不相同,这部分内容大家可以自行探索.
Java 中常常会使用 count++ 的方式来实现计数器的自增操作,直觉上我们认为该操作是“一气呵成”的,但实际上对应的计算机中执行了 3 条指令:
如果运行在同一个核心上的线程 T1 和线程 T2 先后执行 count++ ,可能会存在一种情况:
初始状态下 count 为 0, 我们期望执行结束后线程 T1 的执行结果是 1,线程 T2 的执行结果是 2,但实际上恰恰相反,这就是上下文切换带来的原子性问题.
Tips :上下文切换的内容请参考《 面霸的自我修养:Java线程专题 》.
指令重排是 CPU 一项重要的优化手段,在不改变单线程执行结果的前提下,CPU 可以自行选择如何优化指令。指令重排遵循两个基本原则:
我们举个 Java 中经典的例子,未正确同步的单例模式:
public static class Singleton {
private Singleton instance;
public Singleton getInstance() {
if (instance == null) {
synchronized(this) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
private Singleton() {
}
}
Java 中通过关键字 new 来创建一个对象要经历 3 步:
分析数据依赖原则,操作 1 是要先于操作 2 和操作 3 执行的,操作 2 和操作 3 之间并没有依赖性,如果操作 2 和操作 3 交换了执行顺序,依旧满足单线程环境下的语义,因此,在实际的执行过程中,无论是 1 -> 2 -> 3 还是 1->3->2 都是可以接受的.
那么在这个例子中,可能出现如下情况:
这种情况下线程 T2 拿到的是未经过初始化的 instance 对象.
JMM 提供了一系列 Java 内存交互规范,用于规范不同平台下多线程环境中并发访问共享内存的方式,以保证可见性,原子性和有序性,确保程序的可预测性和可靠性。JMM 中最重要的两个部分是 as-if-serial 语义和 Happens-Before 原则.
Tips : 实际上,JMM 的出现是早于 JSR-133 的,在早期的 Java 语言规范中描述的 JMM 模型存在一定的缺陷,因此在 JSR-133 中对早期的 JMM 模型做了改进,并作为 JSR-176(Java 5.0)的一部分.
难易程度 :❤❤❤❤❤ 重要程度 :❤❤❤ 面试公司 :无 。
Happens-Before 原则是 JMM 中的一部分,用于描述多线程环境下操作之间执行结果的顺序 ,例如:操作 A happens-before 操作 B(记作 \(A\underrightarrow{hb}B\) ),表示无论在何种情况下,操作 A 的结果对操作 B 来说都是可见的.
JSR-133 中定义了 Happens-Before 原则的 6 项内容:
Thread#start
规则 :如果线程 t1 执行操作启动线程 t2(执行 t2.start
),那么 t1 线程的 t2.start
操作happens-before于线程 t2 中的任意操作。 Thread# join
规则 :如果线程 t1 执行操作 t2.join
并成功返回,那么线程B中的任意操作happens-before于线程A从 t2.join
操作成功返回。 Happens-Before 原则提供了线程间的可见性保证,描述了线程执行结果间的先后顺序,但 Happens-Before 原则并不限制指令执行的顺序,即 Happens-Before 原则并不禁止重排序,只是要求重排序后的结果满足Happens-Before 原则的要求 .
这部分我们来讨论在设计锁的过程中的理论基础,了解各种各样的锁的特性.
Tips : 《 一文看懂并发编程中的锁 》中涵盖了大部分 Java 中锁的理论知识.
难易程度 :❤❤❤ 重要程度 :❤❤❤ 面试公司 :无 。
读写锁之间是对访问共享资源“态度”上的差异.
读锁(Read Lock),共享锁(Shared Lock),S 锁,指的是 允许多个线程同时读取共享资源的并发控制机制,读锁在读操作之间是共享的,一旦涉及到写操作就会发生互斥 .
写锁(Write Lock),互斥锁(Mutex Lock),排他锁(Mutex Lock),X 锁,指的是 无论读写同一时间只允许一个线程访问共享资源的并发控制机制 .
Tips :在并发编程中,你会常常看到临界区这个词, 临界区指的是访问共享资源的代码区域 ,如果不能正确的控制这段代码区域的并发访问,可能会导致很多违背直觉的并发问题.
难易程度 :❤❤❤ 重要程度 :❤❤❤❤❤ 面试公司 :蚂蚁金服,联储证券,质数金融 。
悲观锁是一种用于多线程环境中保护共享资源一致性的并发控制机制 。悲观锁总是假设共享资源会被修改,因此在访问(包含读和写两种)共享资源前,先获取锁来保护共享资源,防止其它线程访问共享资源,避免了并发导致的问题.
悲观锁的工作原理如下:
同样的, 乐观锁也是一种用于多线程环境中保护共享资源一致性的并发控制机制 。与悲观锁不同,乐观锁认为共享资源不会被修改,所以在读取时并不会对资源进行上锁,只有在更新资源时,才会对资源进行冲突检测.
乐观锁的工作原理如下:
Tips :这里我们略过了通过共享资源的值比较环节,直接使用了版本号或时间戳.
难易程度 :❤❤❤ 重要程度 :❤❤❤❤❤ 面试公司 :蚂蚁金服,联储证券,质数金融 。
乐观锁与悲观锁适用的场景并不相同,无法绝对的说孰优孰劣。 乐观锁更适用于读多写少的场景,而悲观锁更适用于写多读少的场景 .
乐观锁的特点是假设冲突不会发生,读取共享资源时不会加锁,允许对多线程同时读取共享资源,只在更新数据时检测冲突.
优点:
缺点:
悲观锁的特点是假设冲突总会发生,无论读写操作,都会加锁,同一时间只允许一个线程访问共享资源.
优点:
缺点:
难易程度 :❤❤❤ 重要程度 :❤❤❤❤❤ 面试公司 :无 。
CAS(Compare And Swap),即比较替换。CAS 操作 3 个数:
只有当 V == A 时,才会将 V 的值更新为 B,否则什么都不做.
其原理是, 如果内存中值与线程取出的值相同时,认为在这个期间并没有线程修改共享资源,因此可以对共享数据进行修改 。但如果某个线程先将共享资源从 V 修改到 W,再将共享资源从 W修改会回 V,此时就导致了 ABA 问题.
难易程度 :❤❤❤ 重要程度 :❤❤❤❤❤ 面试公司 :蚂蚁金服 。
ABA 问题是并发编程中的经典问题,通常在使用 CAS 时会产生 ABA 问题:
看起来线程 T1 的修改路径是从 A -> C,实际上是从 A -> B -> A -> C.
为了解决 ABA 的问题,可以通过为共享资源添加版本号(或时间戳),写入操作时比较线程获取到共享资源的版本号与内存中共享资源的版本号是否一致,如果一致则允许更新共享资源,同时需要更新共享资源的版本号,否则不允许更新.
难易程度 :❤❤❤ 重要程度 :❤❤❤❤❤ 面试公司 :美团 。
公平锁与非公平锁的区别在于获取锁的顺序上。 公平锁按照线程申请锁的顺序,依次排队获取锁;非公平锁则不考虑申请顺序,当锁处于空闲状态时,线程可以直接“抢夺”锁的使用权 .
公平锁按照申请顺序获取锁的使用权,正常情况下每个线程都能在可预期的时间内获取到锁;非公平锁可能会出现虽然线程申请锁的时间非常早,但始终无法抢占到锁,导致线程饥饿 .
非公平锁的优点在于无需维护等待队列,在加锁和解锁的速度上优于公平锁 .
Tips :在简单的非公平锁实现中,抢占锁失败的线程调用 Object#wait 进入阻塞状态;释放锁后,调用 Object#notifyAll 唤醒所有线程继续抢夺,无需引入等待队列.
难易程度 :❤❤❤ 重要程度 :❤❤❤❤❤ 面试公司 :蚂蚁金服,美团 。
可重入锁,POSIX 标准中称为递归锁,指的是允许同一个线程多次获取同一个锁。可重入锁可以解决递归调用或嵌套调用中的死锁问题,例如:递归的方式删除指定路径下的所有文件:
private static void deleteFile(File directory) {
synchronized (LOCK) {
File[] files = directory.listFiles();
for (File subFile : files) {
if(subFile.isDirectory()) {
deleteFile(subFile);
} else {
subFile.delete();
}
}
}
}
如果 synchronized 不具备可重入性,那么在遇到第一个子文件夹时程序会被阻塞,导致程序无法继续进行.
可重入锁的设计中,通常会在内部维护一个计数器,每次进入可重入锁时计数器加1,退出时计数器减1,进入和退出的次数需要匹配 .
难易程度 :❤❤❤ 重要程度 :❤❤❤ 面试公司 :无 。
Java 中有 3 个常见的锁,分别是 synchronized,ReentrantLock,ReentrantReadWriteLock,以及一个“小众的的StampedLock.
特点 | synchronized | ReentrantLock | ReentrantReadWriteLock | StampedLock |
---|---|---|---|---|
公平锁 | 非公平锁 | 公平模式/非公平模式 | 公平模式/非公平模式 | 非公平锁 |
可重入性 | 可重入 | 可重入 | 可重入 | 可重入 |
乐观锁 | 悲观锁 | 悲观锁 | 悲观锁 | 乐观锁 |
互斥锁 | 是 | 是 | ReadLock 共享/WriteLock 互斥 | ReadLock 共享/WriteLock 互斥 |
难易程度 :❤❤❤❤ 重要程度 :❤❤❤❤❤ 面试公司 :蚂蚁金服,美团 。
首先考虑使用场景,读多写少可以选择读写锁(读共享,写互斥),写多读少可以直接选择互斥锁;其次添加特性,如果需要支持递归加锁或嵌套加锁就需要添加可重入性,选择公平性或非公平性,最后还可以考虑加锁失败时的设计,如果锁定的临界区非常“小”,锁会在极短时间内释放,可以考虑使用自旋,否则可以选择阻塞.
抛砖引玉,我这里使用 AQS 完成了一个非公平,不可重入的互斥锁:
public class MutexLock {
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(0);
}
private final Sync sync = new Sync();
static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
Thread currentThread = Thread.currentThread();
if (compareAndSetState(0, arg)) {
setExclusiveOwnerThread(currentThread);
return true;
} else {
return false;
}
}
@Override
protected boolean tryRelease(int arg) {
if (getState() != 1) {
return false;
}
setState(arg);
setExclusiveOwnerThread(null);
return true;
}
}
}
Tips :AQS 的问题我们后面还会再讨论.
如果本文对你有帮助的话,还请多多点赞支持。如果文章中出现任何错误,还请批评指正。 最后欢迎大家关注分享硬核 Java 技术的金融摸鱼侠 王有志 ,我们下次再见! 。
最后此篇关于面霸的自我修养:JMM与锁的理论的文章就讲到这里了,如果你想了解更多关于面霸的自我修养:JMM与锁的理论的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
简单的单词自动完成功能仅显示与已输入字符相匹配的单词列表。但是我想根据单词出现的概率对自动完成列表中的单词进行排序,这取决于之前输入的单词,依赖于文本语料库的统计模型。为此我需要什么算法和数据结构?你
今天再来说下网站推广的五种基本方式。可能今天的文章比较宏观,按民间的说法就是比较“虚”,所以不愿看此类文章的同志请慎重。个人认为,要想把推广工作做好,不能只懂得务实,以为成天闷头干活就成了;一定要对
引言 众所周知,数据流分析是实现污点分析的一种常用技术 数据流分析分为过程内的数据流分析与过程间的数据流分析。前者是对一个方法体内的数据流分析,主要是基于CFG分析,不涉及方法调用;后者是基于不同
本文分享自华为云社区《大模型LLM之分布式训练》,作者: 码上开花_Lancer。 随着语言模型参数量和所需训练数据量的急速增长,单个机器上有限的资源已无法满足大语言模型训练的要求。需要设计分布式训
本文分享自华为云社区《五大基础算法--动态规划法》,作者: 大金(内蒙的)。 一、基本概念 动态规划法,和分治法极其相似。区别就是,在求解子问题时,会保存该子问题的解,后面的子问题求解时,可以直接拿来
**摘要:**介绍了Angular中依赖注入是如何查找依赖,如何配置提供商,如何用限定和过滤作用的装饰器拿到想要的实例,进一步通过N个案例分析如何结合依赖注入的知识点来解决开发编程中会遇到的问题。 本
使用 xUnit 2.4.1,我正在寻找一种方法让它在动态跳过集成测试时执行我的扭曲命令。 我知道 xUnit 不是为集成测试而设计的,但我无意将 xUnit 用于某些测试,不是-xUnit 用于其他
我遇到了这个问题; “无损压缩算法声称可以保证使某些文件变小而没有文件变大。 这是; a) Impossible b) Possible but may run for an indeterminat
指向结构的指针如此频繁,以至于有一个特殊的运算符:->。以下表达式是等效的: (*x).y x->y 将这个运算符简单地定义为这样的预处理器宏是否公平: #define (x)-> (*(x).)
关闭。这个问题是off-topic .它目前不接受答案。 想改善这个问题吗? Update the question所以它是 on-topic对于堆栈溢出。 11 年前关闭。 Improve this
我正在使用各种 jquery 插件,包括延迟加载、scrollTo、图像效果以及从阅读本网站上的人们的帖子中收集的各种片段。现在,所有这些效果都从页脚处发出,并且出现了滞后现象。效果就像是,犹豫,或神
我已经搜索了 1 个小时来了解(并获得可行的来源)调整大小在 CSS(媒体)中发生时如何触发以及由哪个组件触发。 我想要的是它在浏览器中的工作方式,调整窗口大小时它的作用。不是代码。 这样做的目的是创
我有一个问题,我不想实现。我只是想在我的推理中得到一点帮助。 我想确定两个对象是否重叠(它们的 x 和 y 坐标,以及它们的高度和宽度都存储在一个 vector 中),然后,如果重叠,则将它们从当前
我的问题实际上与代码无关,我知道如何反转字符串。 这是一个非常常见的面试问题,所以我想澄清一下我认为正确的解决方案是什么。 所以前提开始了,你在字符串的末尾有一个计数器,然后在开头有一个。然后交换它们
我正在尝试在 Android 上编写一个 GPS 跟踪(类似于慢跑应用程序),但 GPS 位置抖动问题使它变得丑陋。当精度为 FINE 且精度在 5 米以内时,位置每秒抖动 1-n 米。您如何从合法运
这个问题在这里已经有了答案: Why does adding 0.1 multiple times remain lossless? (3 个答案) 关闭 8 年前。 以下表达式返回 false(例
问答系统(Question Answering System,QA) 是信息检索系统的一种高级形式,它能用准确、简洁的自然语言回答用户用自然语言提出的问题。其研究兴起的主要原因是人们对快速、准确地获
NLP问答任务 相似度和规则匹配,都是早期的方法,现在主流的方法,都是基于生成的方法 结构化数据问答,有两种形式,一种是知识图谱形式、一种是关系型数据库形式。 主要应用在企业中,减少销售的
什么是任务型对话: 任务型:用于帮助用户完成某领域的特定任务,例如订餐、查天气、订票等 闲聊型:也称作开放域对话系统,目标是让用户持续的参与到交互过程,提供情感陪伴 问答
常见的对话系统 个人助手 •Apple Siri, Amazon Alexa, Baidu Xiaodu 客户服务•餐厅预定、商品咨询、债务催收 休闲娱乐 •Micr
我是一名优秀的程序员,十分优秀!