jvm与锁

今天是《面霸的自我修养》的第二弹,内容是Java并发编程中关于Java内存模型(Java Memory Model)和锁的基础理论相关的问题。这两块内容的八股文倒是不多,但是难度较大,接下来我们就一起一探究竟吧。

数据来源:

  • 大部分来自于各机构(Java之父,Java继父,某灵,某泡,某客)以及各博主整理文档;
  • 小部分来自于我以及身边朋友的实际经理,题目上会做出标识,并注明面试公司。

叠“BUFF”:

  • 八股文通常出现在面试的第一二轮,是“敲门砖”,但仅仅掌握八股文并不能帮助你拿下Offer;
  • 由于本人水平有限,文中难免出现错误,还请大家以批评指正为主,尽量不要喷~~
  • 本文及历史文章已经完成PDF文档的制作,提取关键字【面霸的自我修养】。

难易程度:🔥🔥🔥🔥🔥

重要程度:🔥🔥🔥🔥🔥

公司:阿里巴巴

Java内存模型篇

关于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内存模型(Java Memory Model,JMM)是Java语言规范中的一套规则,它描述了多线程环境下的线程与内存(主内存和高速缓存)的交互方式,以保证可见性,有序性和原子性,同时它屏蔽了硬件与操作系统的底层差异,使得Java程序在所有平台下的内存访问效果一致。

高速缓存带来的可见性问题

我们知道,CPU的运算速度是远高于内存读写速度的,为了减少速度间的差异,CPU为每个核心引入了高速缓存(通常分为L1,L2和L3)。多线程的程序中,线程可能会运行在不同的核心上,这时它们使用自己缓存中从主内存拷贝的数据副本,假设每个CPU只有一个高速缓存,画一个简易的模型:

如果线程T1和线程T2分别从主内存中读取同一个数据的到自己的高速缓存中进行操作,如果线程T1是先于线程T2发生的,那么此时线程T2无法感知到线程T1对缓存中数据做出的修改,导致可线程间的可见性问题。

Tips:虽然硬件层面引入了缓存一致性协议,但仍旧存在可见性问题,另外不同的CPU架构对缓存一致性协议的实现不同导致出现的问题也不相同,这部分内容大家可以自行探索。

上下文切换带来的原子性问题

Java中常常会使用count++的方式来实现计数器的自增操作,直觉上我们认为该操作是“一气呵成”的,但实际上对应的计算机中执行了3条指令:

  • 指令1:将count读入缓存;
  • 指令2:执行自增操作;
  • 指令3:将自增后的count写入内存。

如果运行在同一个核心上的线程T1和线程T2先后执行count++,可能会存在一种情况:

初始状态下count为0,我们期望执行结束后线程T1的执行结果是1,线程T2的执行结果是2,但实际上恰恰相反,这就是上下文切换带来的原子性问题。

Tips:上下文切换的内容请参考《面霸的自我修养:Java线程专题》。

指令重排带来的有序性问题

指令重排是CPU一项重要的优化手段,在不改变单线程执行结果的前提下,CPU可以自行选择如何优化指令。指令重排遵循两个基本原则:

  • 数据依赖原则:如果两个操作使用的数据存在依赖性,那么不能通过指令重排来优化这两个操作的执行顺序;
  • as-if-serial语义:无论如何重排序,都要保证单线程场景下的语义不能被改变(或者说执行结果不变)。

我们举个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. 将变量名指向这块内存。

分析数据依赖原则,操作1是要先于操作2和操作3执行的,操作2和操作3之间并没有依赖性,如果操作2和操作3交换了执行顺序,依旧满足单线程环境下的语义,因此,在实际的执行过程中,无论是1->2->3还是1->3->2都是可以接受的。

那么在这个例子中,可能出现如下情况:

这种情况下线程T2拿到的是未经过初始化的instance对象。

JMM的理解

JMM提供了一系列Java内存交互规范,用于规范不同平台下多线程环境中并发访问共享内存的方式,以保证可见性,原子性和有序性,确保程序的可预测性和可靠性。JMM中最重要的两个部分是as-if-serial语义和Happens-Before原则。

Tips:实际上,JMM的出现是早于JSR-133的,在早期的Java语言规范中描述的JMM模型存在一定的缺陷,因此在JSR-133中对早期的JMM模型做了改进,并作为JSR-176(Java 5.0)的一部分。


Happens-Before原则

难易程度:🔥🔥🔥🔥🔥

重要程度:🔥🔥🔥

公司:美团,爱奇艺,阿里巴巴

Happens-Before原则是JMM中的一部分,用于描述多线程环境下操作之间执行结果的顺序,例如:操作A happens-before操作B(记作

),表示无论在何种情况下,操作A的结果对操作B来说都是可见的。

JSR-133中定义了Happens-Before原则的6项内容:

  • 程序顺序规则:线程中的每个操作happens-before该线程中的任意后续操作。
  • 监视器锁规则:锁的解锁happens-before随后这个锁的加锁。
  • volatile变量规则:volatile变量的写happens-before后续任意对这个volatile变量的读。
  • 传递性:如果

    ,且,那么。
  • 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)

读锁(Read Lock),共享锁(Shared Lock),S锁,指的是允许多个线程同时读取共享资源的并发控制机制,读锁在读操作之间是共享的,一旦涉及到写操作就会发生互斥

写锁(Write Lock)

写锁(Write Lock),互斥锁(Mutex Lock),排他锁(Mutex Lock),X锁,指的是无论读写同一时间只允许一个线程访问共享资源的并发控制机制

Tips:在并发编程中,你会常常看到临界区这个词,临界区指的是访问共享资源的代码区域,如果不能正确的控制这段代码区域的并发访问,可能会导致很多违背直觉的并发问题。


🔥什么是乐观锁?什么是悲观锁?

难易程度:🔥🔥🔥

重要程度:🔥🔥🔥🔥🔥

公司:蚂蚁金服,联储证券,质数金融

悲观锁(Pessimistic Locking)

悲观锁是一种用于多线程环境中保护共享资源一致性的并发控制机制。悲观锁总是假设共享资源会被修改,因此在访问(包含读和写两种)共享资源前,先获取锁来保护共享资源,防止其它线程访问共享资源,避免了并发导致的问题。

悲观锁的工作原理如下:

  • 线程尝试获取保护共享资源的锁;
    • 获取锁成功,允许线程访问共享资源;
    • 获取锁失败,线程阻塞,等待锁的释放;
  • 获取到锁的线程操作完毕后释放锁。
乐观锁(Optimistic Locking)

同样的,乐观锁也是一种用于多线程环境中保护共享资源一致性的并发控制机制。与悲观锁不同,乐观锁认为共享资源不会被修改,所以在读取时并不会对资源进行上锁,只有在更新资源时,才会对资源进行冲突检测。

乐观锁的工作原理如下:

  • 允许多线程同时读取共享资源;
  • 写入共享资源时通过自身持有资源的标识(版本号,时间戳等)与共享资源的标识进行对比;
    • 标识未发生改变,允许线程写入资源;
    • 标识发生改变,禁止线程陷入资源;
  • 写入资源失败的的线程,允许进行重试或直接抛出异常。

Tips:这里我们略过了通过共享资源的值比较环节,直接使用了版本号或时间戳。


🔥乐观锁一定优于悲观锁吗?

难易程度:🔥🔥🔥

重要程度:🔥🔥🔥🔥🔥

公司:蚂蚁金服,联储证券,质数金融

乐观锁与悲观锁适用的场景并不相同,无法绝对的说孰优孰劣。乐观锁更适用于读多写少的场景,而悲观锁更适用于写多读少的场景

乐观锁

乐观锁的特点是假设冲突不会发生,读取共享资源时不会加锁,允许对多线程同时读取共享资源,只在更新数据时检测冲突。

优点:

  • 允许多线程同时访问共享资源,不会加锁,性能较好
  • 不会引起死锁

缺点:

  • 写多读少的场景中,频繁的重试会带来额外的性能开销
悲观锁

悲观锁的特点是假设冲突总会发生,无论读写操作,都会加锁,同一时间只允许一个线程访问共享资源。

优点:

  • 只允许一个线程访问共享资源,避免了乐观锁在多线程写入时频繁重试带来的性能消耗

缺点:

  • 无论读写都会加锁,实际上相当于单线程访问共享资源,会成为高并发系统的性能瓶颈
  • 如果业务中存在多个锁,可能会导致死锁的发生

什么是CAS?

难易程度:🔥🔥🔥

重要程度:🔥🔥🔥🔥🔥

公司:无

CAS(Compare And Swap),即比较替换。CAS操作3个数:

  • 内存值V,即内存中共享资源的值;
  • 旧的预期值A,即线程从内存中读取到且未进行修改共享资源的值;
  • 要修改的值B,即线程进行修改后的共享资源的值。

只有当V == A时,才会将V的值更新为B,否则什么都不做。

其原理是,如果内存中值与线程取出的值相同时,认为在这个期间并没有线程修改共享资源,因此可以对共享数据进行修改。但如果某个线程先将共享资源从V修改到W,再将共享资源从W修改会回V,此时就导致了ABA问题。


🔥乐观锁如何解决ABA问题?

难易程度:🔥🔥🔥

重要程度:🔥🔥🔥🔥🔥

公司:蚂蚁金服

ABA问题是并发编程中的经典问题,通常在使用CAS时会产生ABA问题:

  • 假设存在共享资源A;
  • 线程T1读取A;
  • 线程T2读取A后,将其修改为B,并写入内存;
  • 线程T3读取B后,将其修改为A,并写入内存;
  • 线程T1将A修改为C,并通过CAS将C写入内存。

看起来线程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中提供了哪些锁?

难易程度:🔥🔥🔥

重要程度:🔥🔥🔥

公司:无

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 {@Overrideprotected boolean tryAcquire(int arg) {Thread currentThread = Thread.currentThread();if (compareAndSetState(0, arg)) {setExclusiveOwnerThread(currentThread);return true;} else {return false;}}@Overrideprotected boolean tryRelease(int arg) {if (getState() != 1) {return false;}setState(arg);setExclusiveOwnerThread(null);return true;}}
}

Tips:AQS的问题我们后面再讨论。

参考

  • JSR-133: Java Memory Model and Thread Specification
  • JSR 133 (Java Memory Model) FAQ
  • 深入理解Java内存模型
  • 深入理解JMM和Happens-Before
  • 一文看懂并发编程中的锁

如果本文对你有帮助的话,还请多多点赞支持。如果文章中出现任何错误,还请批评指正。最后欢迎大家关注分享硬核Java技术的金融摸鱼侠王有志,我们下次再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/113735.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HCIP-HCS华为私有云的使用

1、概述 华为公有云(HC)、华为私有云(HCS)华为混合云(HCSO)。6.3 之前叫FusionSphere OpenStack,6.3.1 版本开始叫FusionCloud,6.5.1 版本开始叫Huawei Cloud Stack (HCS)华为私有云…

算法通关村第9关【黄金】| 两道有挑战的问题

1. 将有序数组转换为二叉搜索树 思路:二分法,这个算法保证了每次选择的中间元素都能保持左右子树的高度差不超过 1,从而构建一个高度平衡的二叉搜索树。这个过程类似于分治法,通过递归不断将大问题分解成小问题并解决。 找到数组…

链路聚合原理

文章目录 一、定义二、功能三、负载分担四、分类五、常用命令 首先可以看下思维导图,以便更好的理解接下来的内容。 一、定义 在网络中,端口聚合是一种将连接到同一台交换机的多个物理端口捆绑在一起,形成一个逻辑端口的技术。通过端口聚合&…

Redis 主从复制和哨兵模式

一、概念 主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower)。数据的复制是单向的,只能由主节点…

学习JAVA打卡第四十五天

StringBuffer类 StringBuffer对象 String对象的字符序列是不可修改的,也就是说,String对象的字符序列的字符不能被修改、删除,即String对象的实体是不可以再发生变化,例如:对于 StringBuffer有三个构造方法&#xff…

JPA在不写sql的情况下实现模糊查询

本文已收录于专栏 《Java》 目录 背景介绍概念说明单字段模糊匹配:多字段模糊匹配: 实现过程代码实现1.写一个实体类去实现Specification接口,重写toPredicate方法2.定义一个接口去继承JpaRepository接口,并指定返回的类型和参数类…

家宽用户家庭网的主要质量问题是什么?原因有哪些

1 引言 截至2020年底,我国家庭宽带(以下简称“家宽”)普及率已达到96%。经过一年多的发展,当前,家庭宽带的市场空间已经饱和。运营商在家宽市场的竞争也随之从新增用户数的竞争转移到家宽品质的竞争。 早期运营商的家…

多张图片转为pdf怎么弄?

多张图片转为pdf怎么弄?在网络传输过程中,为了避免图片格式文件出现差错,并确保图片的清晰度和色彩不因不同设备而有所改变,常见的做法是将图片转换为PDF格式。然而,当涉及到多张图片时,逐一转换将会变得相…

IP基本原理(上)

文章目录 一、IP的定义二、IP的作用1.标识节点和链路2.寻址和转发3.适应各种数据链路 三、IP头部封装格式四、MTU五、IP地址1.定义2.格式2.1 点分十进制和二进制关系与转换2.2 由网络位主机位组成2.3 网络位长度决定网段 3.分类3.1 A类3.2 B类3.3 C类3.4 D类3.5 E类 4.特殊地址…

职场中的团队建设:超越任务,铸就默契

团队建设在职场中的重要性日益凸显。无论是初创公司还是大型企业,都需要一个高效、和谐且有创新能力的团队来推动业务发展。本文将深入探讨团队建设的活动和策略,帮助您构建一个卓越的团队。 1. 团队建设的重要性 提高团队凝聚力 团队凝聚力不仅仅是团…

手写数字识别之网络结构

目录 手写数字识别之网络结构 数据处理 经典的全连接神经网络 卷积神经网络 手写数字识别之网络结构 无论是牛顿第二定律任务,还是房价预测任务,输入特征和输出预测值之间的关系均可以使用“直线”刻画(使用线性方程来表达&#xff09…

SSM - Springboot - MyBatis-Plus 全栈体系(三)

第二章 SpringFramework 一、技术体系架构 1. 总体技术体系 1.1 单一架构 一个项目,一个工程,导出为一个war包,在一个Tomcat上运行。也叫all in one。 单一架构,项目主要应用技术框架为:Spring , SpringMVC , Myba…

基于Jenkins构建生产CICD环境(第三篇)

目录 基于Jenkins自动打包并部署docker环境 1、安装docker-ce 2、阿里云镜像加速器 3、构建tomcat 基础镜像 4、构建一个Maven项目 基于Jenkins自动化部署PHP环境 基于rsync部署 基于Jenkins自动打包并部署docker环境 1、安装docker-ce 在192.168.2.123 机器上&#x…

Qt中XML文件创建及解析

一 环境部署 QT的配置文件中添加xml选项&#xff1a; 二 写入xml文件 头文件&#xff1a;#include <QXmlStreamWriter> bool MyXML::writeToXMLFile() {QString currentTime QDateTime::currentDateTime().toString("yyyyMMddhhmmss");QString fileName &…

【算法日志】动态规划刷题:股票买卖问题(day41)

代码随想录刷题60Day 目录 前言 买卖股票的最佳时机1 买卖股票的最佳时机2 买卖股票的最佳时机3 买卖股票的最佳时机4 前言 本日着重于多状态问题的处理&#xff0c;各状态之间会有一定联系&#xff0c;状态转移方程将不再局限一个。 买卖股票的最佳时机1 int maxProfit(…

详细对比超融合服务器硬件平滑升级方案:新建集群 VS 滚动升级

作者&#xff1a;深耕行业的金融团队 刘慧敏 在企业 IT 基础架构运维中&#xff0c;经常会遇到以下问题&#xff0c;从而需要对服务器硬件进行更换或升级&#xff1a; 服务器达到维护期限&#xff1a;通常在金融行业中&#xff0c;生产环境的服务器维护期限在 5 年左右&#…

三十七个常见Vue面试题,背就完事了二

八、vue.mixin的使用场景和原理? Vue的mixin的作用就是抽离公共的业务逻辑&#xff0c;原理类似对象的继承&#xff0c;当组件初始化的时候&#xff0c;会调用mergeOptions方法进行合并&#xff0c;采用策略模式针对不同的属性进行合并。 如果混入的数据和本身组件的数据有冲突…

《向量数据库》——为何向量数据库对大模型LLM很重要?

当您浏览Twitter、LinkedIn或新闻源上的时间轴时,可能会看到一些关于聊天机器人、LLM和GPT的内容。因为每周都有新的LLM发布,很多人都在谈论LLM。 我们目前置身于一场人工智能革命,许多新应用都依赖于向量嵌入。不妨让我们更多地了解向量数据库以及为什么它们对LLM很重要。…

图书管理系统Java书店进销存jsp源代码MySQL

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 图书管理系统 系统有1权限&#xff1a;管理员 用所技…

产能紧张,联电、日月光急单要涨价 | 百能云芯

台积电在CoWoS先进封装领域的产能紧张&#xff0c;这导致英伟达在AI芯片方面的生产受到限制。有消息称&#xff0c;英伟达正考虑通过加价寻找除台积电以外的替代生产能力&#xff0c;以应对这一局面。这一消息引发了巨大的订单涌入效应。 联电公司作为提供CoWoS中间层材料的供应…