【从零开始学习JAVA | 第四十一篇】深入JAVA锁机制
目录
前言:
引入:
锁机制:
CAS算法:
乐观锁与悲观锁:
总结:
前言:
在多线程编程中,线程之间的协作和资源共享是一个重要的话题。当多个线程同时操作共享数据时,就可能引发数据不一致或竞态条件等问题。为了解决这些问题,Java提供了强大的锁机制,使得多线程程序能够安全地共享资源、实现线程间的同步。
Java锁机制允许我们控制多个线程对共享资源的访问,确保在任何时刻只有一个线程可以访问公共数据或执行特定的代码块。这种机制既可以用于保护共享变量的一致性,也可以用于实现对临界区的互斥访问。

引入:
在锁机制没有出现以前,多线程往往会出现以下两个问题:
1.数据不一致:当多个线程同时读写共享数据时,可能会出现数据不一致的情况。比如,一个线程正在对某个变量执行修改操作,而另一个线程同时读取该变量的值,如果没有锁机制的保护,可能会读取到未被修改之前的旧值,导致数据出现不一致的情况。
假设有两个线程同时对共享的变量进行读取和修改操作:
int sharedVariable = 0;// 线程1的代码
sharedVariable = 10;// 线程2的代码
int value = sharedVariable;
System.out.println(value);
在上述代码中,线程1将共享变量 sharedVariable 的值修改为10。同时,线程2读取共享变量 sharedVariable 的值并打印出来。如果没有适当的同步机制,线程2可能会读取到修改之前的旧值0,导致数据不一致的情况
2.竞态条件:当多个线程同时对共享资源进行修改操作时,由于线程之间执行顺序的不确定性,可能导致执行结果依赖于线程执行时的相对顺序。这种不确定性可能引发竞态条件,导致程序出现错误的行为。例如,多个线程同时对同一个计数器进行自增操作,如果没有适当的同步机制,就可能出现计数器值不正确的情况。
假设有两个线程同时对一个计数器进行自增操作:
int counter = 0;// 线程1的代码
counter++;// 线程2的代码
counter++;
在上述代码中,如果没有适当的同步机制,两个线程在执行 counter++ 操作时可能会发生线程切换,导致线程1和线程2之间的执行顺序不确定。这种情况下,如果线程1先执行自增操作,然后线程2执行自增操作,最终计数器的值可能只增加了1,而不是期望的2。这就是典型的竞态条件,导致了程序行为的错误。
因此我们需要一种方法,可以在其他线程被调用的时候对调用数据进行保护,所以我们创造出了锁机制,通过使用锁机制,可以解决这些问题。锁机制可以确保在某个线程修改共享数据时,其他线程无法同时进行读取或修改操作,从而避免了数据不一致和竞态条件的发生。
在正式介绍锁机制之前,我们还是先来认识一下JVM运行时的内存结构:

在这张图中,我们需要知道
- 绿色部分是所有线程共享的数据区域
- 黄色部分是每一个线程单独享有的数据区域
那让我们开始正式的介绍锁:
锁机制:
在JAVA中,每一个对象都有一把锁,这把锁被存放在对象头中,锁中记录了当前对象被哪个线程占用。
对象的组成部分:
对象头(Object Header):对象头包含了一些用于存储对象元数据的信息,如对象的哈希码、锁的信息、GC(垃圾回收)相关的标记等。对象头的大小在不同的Java虚拟机实现中会有所差异。
实例数据(Instance Data):实例数据是对象的成员变量的实际存储空间。它们是对象的状态的一部分,也就是我们定义在类中的成员变量。实例数据的大小取决于对象的成员变量数量和类型。
对齐填充(Alignment Padding):为了提高内存访问的效率,Java虚拟机要求对象的起始地址必须是某个特定值的倍数。如果实例数据的大小不是这个特定值的倍数,就需要通过填充字节来对齐。
OK,那我们开始讲解我们学习中遇到的第一个锁
synchronized
在Java中,当使用synchronized关键字修饰方法或代码块时,编译后会生成两条字节码指令:monitorenter和monitorexit。这两条指令用于获取锁和释放锁,实现线程同步。
-
monitorenter指令:该指令用于获取对象的锁(内置锁)。当线程执行到被synchronized修饰的代码块或方法时,它首先会尝试获取对象的锁。如果该锁没有被其他线程持有,该线程就会成功获取锁,并继续执行下面的指令。如果锁被其他线程持有,则当前线程会进入阻塞状态,直到锁被释放。 -
monitorexit指令:该指令用于释放对象的锁。当线程执行完synchronized修饰的代码块或方法后,或者发生了异常退出时,该线程会释放对象的锁。这样可以确保其他线程能够获取锁并执行相关的代码。
示例:
public class MyClass {private final Object lock = new Object();public void synchronizedMethod() {synchronized (lock) {// 被synchronized修饰的代码块}}
}
对应的编译后的字节码指令如下:
0: aload_0 ; 将当前对象加载到操作数栈
1: getfield #1 ; 加载对象的字段(锁对象)
4: dup ; 复制栈顶元素(锁对象)
5: astore_1 ; 将锁对象存储到局部变量
6: monitorenter ; 进入同步块获取锁
7: /* 同步代码块 */ ; 执行同步块的代码
8: aload_1 ; 加载局部变量(锁对象)
9: monitorexit ; 退出同步块释放锁
这些字节码指令确保了在synchronized修饰的代码块中,只能有一个线程执行,并保证了线程之间的互斥性和正确的内存同步。这样可以确保多个线程安全地访问共享资源,避免并发问题。
而这就是synchronized的运行机制,通过两个字节码来实现对线程的同步机制。
但遗憾的是synchronized存在性能问题,因为他被编译后实际上就是两个字节码指令,而这两个字节码文件都是依赖于操作系统的mutex lock进行的,而JAVA线程本质上就是对操作系统线程的映射。因此每当操作或挂起一个线程,都要对操作系统内核态进行切换,而这种操作太费时间了,在一切情况下甚至切换的时间都超过了应用的时间。
而从JAVA6开始,就对synchronized进行了优化,引入了偏向锁和轻量级锁。
此时锁就一共有四种了:
无锁,偏向锁,轻量级锁,重量级锁
-
无锁(Lock-Free):无锁是一种并发控制机制,允许多个线程同时修改共享资源,而不需要显式地使用锁。无锁的算法通常通过使用原子操作(如CAS,Compare and Swap)来保证多线程操作的原子性和线程安全性。无锁的目标是通过无竞争的方式实现最大的并发性能。
-
偏向锁(Biased Locking):偏向锁是JVM针对没有竞争的场景进行的一种锁优化机制。它的目标是减少无竞争情况下的锁操作开销。在偏向锁状态下,当一个线程访问锁时,JVM会将锁对象的标记置为偏向线程ID,之后该线程再次访问锁时就不会再进行同步操作,从而提高性能。
-
轻量级锁(Lightweight Locking):轻量级锁是针对竞争不激烈的情况下的一种锁优化机制。它通过使用CAS操作来进行锁定和释放,而不需要进行互斥的内核态操作。当一个线程尝试获取轻量级锁时,它会使用CAS操作将对象头中的标志位更新为锁记录(Lock Record)指向的线程ID。如果操作成功,这个线程就可以继续执行临界区代码;如果操作失败,说明存在竞争,需要升级为重量级锁。
-
重量级锁(Heavyweight Locking):重量级锁是传统的锁机制,也是默认的锁实现。当多个线程竞争一个锁时,JVM会将该锁从轻量级锁升级为重量级锁。重量级锁会在操作系统层面进行互斥的内核态操作,如使用互斥量等。它能确保多个线程之间的互斥性,但也会带来更多的开销。
这四个状态是递增的,无锁->偏向锁->轻量级锁->重量级锁。而这种状态可以升级也可以降级。
在我们学习了互斥锁的底层机制,互斥锁的四种状态之后 我们在来介绍一下
CAS算法:
CAS(Compare and Swap)是一种用于实现无锁算法的同步原语。它主要用于多线程环境下对共享数据的原子操作,提供了一种线程安全的方式来进行数据的更新。
CAS 算法涉及三个操作数:内存地址(V)、旧的预期值(A)和新的值(B)。CAS 算法的执行过程如下:
-
首先,线程读取内存地址 V 中的值,记为当前值 currentV。
-
然后,线程检查当前值 currentV 是否等于预期值 A。如果相等,说明没有其他线程修改过该值,线程可以进行更新操作。
-
如果当前值 currentV 不等于预期值 A,说明有其他线程修改过该值,线程不进行更新操作。可以选择重试或采取其他策略来处理。
-
如果当前值 currentV 等于预期值 A,线程将新的值 B 写入到内存地址 V 中。
-
最后,线程判断写入操作是否成功。如果成功,说明更新操作完成;如果不成功,说明有其他线程在该线程之前执行了更新操作,需要重新执行整个 CAS 算法。
CAS 算法的核心思想是通过比较当前值和预期值是否相等来判断共享数据是否被修改过。如果没有被修改过,就进行更新操作;如果被修改过,说明有其他线程先一步修改了数据,需要重试。因此,CAS 算法可以避免传统锁所带来的线程阻塞和上下文切换的开销,增加了并发性能。
然而,CAS 算法也存在一些问题,例如ABA 问题(两次读取的值是一样的,但是中间过程发生了变化)以及循环时间长开销大等。为了解决这些问题,Java 提供了 Atomic 包下的一些原子类,如 AtomicReference 和 AtomicStampedReference,可以解决 CAS 算法中的ABA 问题,并提供了更高级的封装和功能。
我们用图片来演示一下CAS算法:
A和B就代表两条线程,而C就代表此时A和B争抢的资源文件,如果A线程运气好抢到了资源C,他将会把自身的old value 与C进行对比,如果一致,就把C的状态改为1,并且获得对C的操作权。
而此时B再与C进行比较,0!=1,因此B就会放弃swap操作,但是在实际操作中,B并不会就直接放弃,而是让其进行自旋,所谓的自旋就是不断进行CAS操作,假如C的状态后面变为0,B就又会重新进行比较和交换操作
下面我们用一段代码来展示一下CAS函数:
int cas(long * addr ,long oldvalue,long newvalue)
{if(*addr != old)return 0;*addr= new ;return 1;
}
其实这段代码还是有问题的,CAS分为两部分:compare 和 swap ,那么既然这个方法没有进行任何同步操作,那如果A线程获得时间片但对C状态修改的时候,B线程又获得了时间片,此时不就是两个线程AB同时获得了对资源数据的操作权力吗?
但好在CAS早就已经通过底层设计,将赋予了其原子性。
最后我们再介绍一下乐观锁与悲观锁
乐观锁与悲观锁:
乐观锁和悲观锁是两种并发控制的思想,主要应用于多线程环境下对共享数据的访问控制。它们的主要区别在于对于并发冲突的处理策略和机制。
- 悲观锁:
悲观锁的思想是假设在整个数据操作过程中其他线程可能会修改数据,因此在对数据进行操作时默认认为会发生冲突,所以采取阻塞等待的方式。悲观锁主要通过线程阻塞、锁定共享资源等方式来保证同一时间只有一个线程能够访问共享数据。
常见的悲观锁实现包括:
- 互斥锁(如 Java 中的 synchronized 关键字、ReentrantLock):使用互斥锁来保证对共享资源的独占访问,其他线程需要等待锁释放才能访问。
- 读写锁(如 Java 中的 ReentrantReadWriteLock):通过区分读操作和写操作,允许多个线程同时读取共享资源,但只允许单个线程进行写操作。
- 乐观锁:
乐观锁的思想是假设在整个数据操作过程中不会发生并发冲突,因此不采取阻塞等待的方式,而是在更新数据时检查是否发生冲突。如果发现冲突,则采取相应的策略(如重试或放弃更新)。乐观锁通常使用无锁算法(如 CAS)来实现。
乐观锁在大多数情况下用的是CAS无锁算法,因此不要看见锁这个字就认为乐观锁是锁!
常见的乐观锁实现包括:
- 版本号(Versioning):在数据记录中加入版本号字段,每次更新时通过比较版本号判断是否发生冲突。
- 时间戳(Timestamp):在数据记录中加入时间戳字段,每次更新时通过比较时间戳判断是否发生冲突。
- CAS(Compare and Swap):使用原子操作的方式进行数据的更新,通过比较当前值和预期值是否一致来判断是否发生冲突。
乐观锁适合于读操作非常频繁,但写操作相对较少的场景,可以提高并发性能。然而,乐观锁需要保证数据不会被并发修改的假设成立,否则会引发数据不一致问题。如果冲突频率较高,乐观锁可能会引起大量的重试,降低性能。
在实际应用中,选择悲观锁还是乐观锁要根据具体的场景,考虑并发冲突的频率、数据一致性要求以及性能需求等因素。有时也可以结合两者的优点,使用适当的锁机制来满足需求。
总结:
锁的底层确实很复杂,我们也不是一篇两篇文章就可以讲清楚的,因此我写这篇文章更多的还是为了吸引大家的兴趣,如果有兴趣了可以再去深入的了解一下各种锁。
如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

相关文章:
【从零开始学习JAVA | 第四十一篇】深入JAVA锁机制
目录 前言: 引入: 锁机制: CAS算法: 乐观锁与悲观锁: 总结: 前言: 在多线程编程中,线程之间的协作和资源共享是一个重要的话题。当多个线程同时操作共享数…...
Playable 动画系统
Playable 基本用法 Playable意思是可播放的,可运行的。Playable整体是树形结构,PlayableGraph相当于一个容器,所有元素都被包含在里面,图中的每个节点都是Playable,叶子节点的Playable包裹原始数据,相当于输…...
深入理解Linux内核--虚拟文件
虚拟文件系统(VFS)的作用 虚拟文件系统(Virtual Filesystem)也可以称之为虚拟文件系统转换(Virtual Filesystem Switch,VFS), 是一个内核软件层, 用来处理与Unix标准文件系统相关的所有系统调用。 其健壮性表现在能为各种文件系统提供一个通用的接口。VFS支持的文件…...
记一次 .NET 某外贸ERP 内存暴涨分析
一:背景 1. 讲故事 上周有位朋友找到我,说他的 API 被多次调用后出现了内存暴涨,让我帮忙看下是怎么回事?看样子是有些担心,但也不是特别担心,那既然找到我,就给他分析一下吧。 二࿱…...
关于安卓打包生成aar,jar实现(一)
关于安卓打包生成aar,jar方式 背景 在开发的过程中,主项目引入三方功能的方式有很多,主要是以下几个方面: (1)直接引入源代码module(优点:方便修改源码,易于维护&#…...
QString字符串与16进制QByteArray的转化,QByteArray16进制数字组合拼接,Qt16进制与10进制的转化
文章目录 QString转16进制QByteArry16进制QByteArray转QStringQByteArray16进制数拼接Qt16进制与10进制的转化在串口通信中,常常使用QByetArray储存数据,QByteArray可以看成字节数组,每个索引位置储存一个字节也就是8位的数据,可以储存两位16进制数,可以用uint8取其中的数…...
ElasticSearch安装与启动
ElasticSearch安装与启动 【服务端安装】 1.1、下载ES压缩包 目前ElasticSearch最新的版本是7.6.2(截止2020.4.1),我们选择6.8.1版本,建议使用JDK1.8及以上。 ElasticSearch分为Linux和Window版本,基于我们主要学习…...
JavaWeb中Json传参的条件
JavaWeb中我们常用json进行参数传递 对应的注释为RequestBody 但是json传参是有条件的 最主要是你指定的实体类和对应的json参数能否匹配 1.属性和对应的json参数名称对应 2.对应实体类实现了Serializable接口,可以进行序列化和反序列化,这个才是实体类转…...
包装类+初识泛型
目录 1 .包装类 1.1 基本数据类型对应的包装类 1.2.1装箱 1.2.2拆箱 2.初识泛型 2.1什么是泛型 2.2泛型类 2.3裸类型 2.4泛型的上界 2.5泛型方法 1 .包装类 基本数据类型所对应的类类型 在 Java 中,由于基本类型不是继承自 Object ,为了在泛型…...
基于改进的长短期神经网络电池电容预测,基于DBN+LSTM+SVM的电池电容预测
目录 背影 摘要 LSTM的基本定义 LSTM实现的步骤 基于长短期神经网络LSTM的客电池电容预测 完整代码: 基于长短期神经网络LSTM的公交站客流量预测资源-CSDN文库 https://download.csdn.net/download/abc991835105/88184734 效果图 结果分析 展望 参考论文 背影 为增加电动车行…...
Python 2.x 中如何使用pandas模块进行数据分析
Python 2.x 中如何使用pandas模块进行数据分析 概述: 在数据分析和数据处理过程中,pandas是一个非常强大且常用的Python库。它提供了数据结构和数据分析工具,可以实现快速高效的数据处理和分析。本文将介绍如何在Python 2.x中使用pandas进行数据分析&am…...
获取Spring中bean工具类
获取Spring中bean工具类 工具类 package com.geekmice.springbootselfexercise.utils;import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org…...
【实战篇】亿级高并发电商项目(新建 ego_pojo、ego_mapper、ego_api、ego_provider、搭建后台项目 )十五
目录 八、 搭建 Provide 1 新建 ego_pojo 2 新建 ego_mapper 2.1编写 pom.xml 2.2新建配置文件 编辑...
【Plex】FRP内网穿透后 App无法使用问题
能搜索到这个文章的,应该都看过这位同学的分析【Plex】FRP内网穿透后 App无法使用问题_plex frp无效_Fu1co的博客-CSDN博客 这个是必要的过程,但是设置之后仍然app端无法访问,原因是因为网络端口的问题 这个里面的这个公开端口,可…...
[管理与领导-11]:IT基层管理者 - 目标与落实 - 过程管理失控,结果总难达成的问题思考:如何把过程管控做得更好?
目录 前言: 第1章 问题与现象 1.1 总有意想不到的事发生:意外事件 1.2 总有计划变更:意外影响 1.3 总有一错再错,没有复盘、总结与反思,没有流程与改进 第2章 背后的原因 2.1 缺乏及时的过程检查 - 缺乏异常检测…...
用php语言写一个chatgpt3.5模型的例子
当然可以!使用PHP语言调用OpenAI API与ChatGPT-3.5模型进行交互。首先,确保你已经安装了PHP 7.2或更新版本,并具备可用的OpenAI API密钥。 下面是一个基本的PHP示例,展示了如何使用OpenAI API与ChatGPT-3.5模型进行对话ÿ…...
PHP实现保质期计算器
1.php实现保质期计算, 保质期日期可选,天 、月、年 2. laravel示例 /*** 保质期计算器* return void*/public function expirationDateCal(){$produce_date $this->request(produce_date); // 生产日期$warranty_date $this->reques…...
【独立版】新零售社区团购电商系统生鲜水果商城兴盛优选十荟团源码
【独立版】新零售社区团购电商系统生鲜水果商城兴盛优选十荟团源码...
C++系列十:其他-1. Lua
系列文章目录 Lua 系列文章目录前言Lua介绍:参考链接: 基本语法:函数、迭代器table、userdata、模块元素、元方法:协程、文件读写面向对象、垃圾回收 前言 我写这个博客的一个问题?(●’◡’●) 居然是 取名太难了。 …...
不知道打仗之害,就不知道打仗之利
不知道打仗之害,就不知道打仗之利 【安志强趣讲《孙子兵法》第7讲】 【原文】 夫钝兵挫锐,屈力殚货,则诸侯乘其弊而起,虽有智者,不能善其后矣。 【注释】 屈力殚货:屈力,指力量消耗,…...
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会
在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...
Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...
书籍“之“字形打印矩阵(8)0609
题目 给定一个矩阵matrix,按照"之"字形的方式打印这个矩阵,例如: 1 2 3 4 5 6 7 8 9 10 11 12 ”之“字形打印的结果为:1,…...
