秋招Java后端开发冲刺——并发篇2(JMM与锁机制)
本文对Java的内存管理模型、volatile关键字和锁机制进行详细阐述,包括synchronized关键字、Lock接口及其实现类ReentrantLock、AQS等的实现原理和常见方法。
一、JMM(Java内存模型)
1. 介绍
JMM定义了共享内存中多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性。
2. 模型
- JMM把内存分为两块,一块是私有线程的工作区域(工作内存),一块是所有线程的共享区域(主内存)
- 线程跟线程之间是相互隔离,线程跟线程交互需要通过主内存
二、Volatile关键字
1. 介绍
- Volatile关键字主要用于修饰成员变量(实例变量或类变量),使得对该变量的读写操作直接与主内存交互,而不是通过线程本地的工作内存进行缓存。
- Volatile关键字提供了一种轻量级的同步机制,用于保证多线程环境下数据的可见性(但不能保证数据的原子性,Synchronized关键字既保证可见性,又保证原子性)
2. Volatile禁止指令重排
(1)指令重排
指令重排是一种编译器和处理器优化技术,它改变指令执行的顺序,以提高性能。在单线程环境下,这种优化不会影响程序的正确性。但是在多线程环境下,指令重排可能导致意想不到的结果,破坏程序的正确性。
(2)Volatile如何禁止指令重排
- 通过在读写 volatile 变量时插入内存屏障(Memory Barriers)来实现
- 写屏障(Write Barrier):确保在写 volatile 变量之前的所有写操作在内存中完成。
- 读屏障(Read Barrier):确保在读 volatile 变量之后的所有读操作在内存中完成
- volatile关键字一般加在写变量最后一个,读变量第一个。
class Example {private volatile boolean flag = false;private int a = 0;public void writer() {a = 1; // 1flag = true; // 2}public void reader() {if (flag) { // 3int i = a; // 4}}
}
上述示例中:
- 在writer方法中,写入a = 1(1)操作不能被重排序到写入flag = true(2)之后。因为flag是volatile,在写flag的时候会插入一个StoreStore屏障,确保在flag被写入之前,所有之前的写操作都完成了。
- 在reader方法中,读取flag(3)操作不能被重排序到读取a(4)之前。因为flag是volatile,在读flag的时候会插入一个LoadLoad屏障,确保在flag被读取之后,所有之前的读操作都完成了。
三、锁
1. 悲观锁和乐观锁
(1)悲观锁:认为访问共享资源时一定会发生线程安全问题,因此在访问时必须加锁。
(2)乐观锁:认为访问共享资源时不一定会发生线程安全问题,因此不会加锁,当提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改即可。
(3)二者区别
特性 | 悲观锁 | 乐观锁 |
---|---|---|
锁定机制 | 操作数据前先加锁,其他线程无法操作 | 操作数据时不加锁,提交时检查冲突 |
使用场景 | 适用于写多读少,冲突概率高的场景 | 适用于读多写少,冲突概率低的场景 |
性能 | 由于频繁加锁和释放锁,性能较低 | 不加锁,性能较高 |
实现方式 | 使用数据库锁机制(如行锁、表锁) | 使用版本号或时间戳进行冲突检测 |
数据一致性 | 一致性强,通过锁机制防止并发修改 | 一致性依赖于冲突检测和重试机制 |
并发性 | 并发性差,锁定会阻塞其他线程 | 并发性好,线程可以同时进行操作 |
死锁风险 | 存在死锁风险 | 不存在死锁风险 |
典型应用 | 行锁、表锁、页锁和意向锁(数据库)、ReentrantLock等 | 版本控制系统、CAS(Compare and Swap)操作 |
2. synchronized 关键字
(1)介绍
synchronized 关键字是一种对象锁,采用互斥的方式让同一时刻至多只有一个线程能持有对象锁。
(2)底层实现原理
① Monitor(对象监视器)
- Monitor(监视器)是一种同步机制,用于管理多线程之间的互斥访问和并发控制。Monitor是JVM提供的,但是是基于c++实现的。
- Monitor的结构
- Owner:存储当前获取锁的线程的,只能有一个线程可以获取
- EntryList:关联没有抢到锁的线程,处于Blocked状态的线程
- WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程
② synchronized关键字实现原理
线程获得锁需要使用对象(锁)关联monitor监视器,即获得monitor的持有权。
(3)使用方法
- 同步方法:当一个线程执行同步方法时,它会自动获得该方法所属对象的锁。其他线程在获得该锁之前无法执行该方法。
public class SynchronizedExample {private int count = 0;public synchronized void increment() {count++;}public synchronized int getCount() {return count;}
}
- 同步块:同步块用于在一个方法内同步部分代码,提供了比同步方法更细粒度的控制。
public class SynchronizedBlockExample {private final Object lock = new Object();private int count = 0;public void increment() {synchronized (lock) {count++;}}public int getCount() {synchronized (lock) {return count;}}
}
- 静态同步方法:静态同步方法锁定的是类对象,对于所有该类的实例都有效。
public class StaticSynchronizedExample {private static int count = 0;public static synchronized void increment() {count++;}public static synchronized int getCount() {return count;}
}
3. synchronized锁升级
Monitor实现的锁属于重量级锁,因为monitor是使用c++实现的,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。在JDK 1.6引入了两种新型锁机制:偏向锁和轻量级锁。
(1)对象的内存结构
注:填充为8的整数倍是方便字节对齐提高程序运行效率 ,而且对于压缩指针也更加的方便。
(2)轻量级锁
在无竞争的情况下,轻量级锁使用CAS操作来实现锁的获取和释放,提高运行效率。
① 加锁流程
- 在线程栈中创建一个Lock Record,将其obj字段指向锁对象。
- 通过CAS指令将Lock Record的地址存储在对象头的mark word中,如果对象处于无锁状态则修改成功,代表该线程获得了轻量级锁。
- 如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一部分为null,起到了一个重入计数器的作用。(仍需CAS操作)
- 如果CAS修改失败,说明发生了竞争,需要膨胀为重量级锁。
② 解锁过程
- 遍历线程栈,找到所有obj字段等于当前锁对象的Lock Record。
- 如果Lock Record的Mark Word为null,代表这是一次重入,将obj设置为null后continue。
- 如果Lock Record的 Mark Word不为null,则利用CAS指令将对象头的mark word恢复成为无锁状态。如果失败则膨胀为重量级锁。
(3)偏向锁
与轻量级锁不同的是,偏向锁只第一次获得锁时使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后该线程再获取锁时,只需要判断锁对象mark word中是否是自己的线程id即可。
注:轻量级锁和偏向锁都是在没有竞争的情况下使用,一旦发生了竞争会立刻升级为重量级锁。
4. Lock接口:ReentrantLock
Lock接口提供了一种比传统的同步块和方法更灵活的锁机制。Lock 接口有多种实现,其中最常用的是 ReentrantLock。
(1)Lock接口常见实现类
实现类 | 描述 |
---|---|
ReentrantLock | 可重入的互斥锁,具有与使用 synchronized 方法和语句相同的基本行为和语义,但功能更强大。 |
ReentrantReadWriteLock.ReadLock | 读锁,可被多个读线程同时持有,只要没有写线程持有写锁。 |
ReentrantReadWriteLock.WriteLock | 写锁,独占锁,只有写线程可以持有,读线程和其他写线程都被阻塞。 |
StampedLock | 一种新的读写锁实现,支持乐观读锁和写锁的互斥锁操作,提高并发性和性能。 |
LockSupport | 提供了基本的线程阻塞原语,用于创建锁和其他同步工具。 |
(2)ReentrantLock
① ReentrantLock是一个可重入且独占式的锁,还支持轮询、超时、中断、公平锁和非公平锁等。
② 特点
- 可重入性:一个线程可以多次获得该锁,而不会导致死锁。这意味着同一个线程可以进入锁保护的代码块多次,必须确保在离开代码块时相应的次数解锁。
- 公平性:可以选择公平锁或者非公平锁。公平锁按照线程请求的顺序获取锁,非公平锁则没有这种顺序,可能导致某些线程长时间等待。
- 灵活性:提供了比 synchronized 块更灵活的锁获取方式,如可中断的锁获取、超时的锁获取。
③ 实现原理
- ReentrantLock使用CAS+AQS来实现,使用用一个整数state表示锁的状态,使用一个先进先出的队列(CLH)管理被阻塞的线程。
- 当线程需要获得锁时,使用CAS操作修改锁的状态,修改成功则获得锁;修改失败则加入CLH队列管理。
④ 常用方法
方法 | 描述 |
---|---|
lock() | 获取锁。如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获得锁。 |
unlock() | 释放锁。 |
tryLock() | 尝试获取锁。如果锁在调用时未被另一个线程持有,则获取锁并立即返回 true ;否则返回 false 。 |
tryLock(long time, TimeUnit unit) | 尝试在给定的等待时间内获取锁。如果锁在给定时间内可用,则获取锁并返回 true ;否则返回 false 。 |
lockInterruptibly() | 如果当前线程未被中断,则获取锁。如果锁不可用,则当前线程出于线程调度目的将被禁用并处于休眠状态,直到发生以下两种情况之一:锁由当前线程获得;或者其他某个线程中断当前线程。 |
newCondition() | 返回一个绑定到此 Lock 实例的新 Condition 实例。 |
5. synchronized与ReentrantLock的比较
特性 | synchronized | ReentrantLock |
---|---|---|
锁类型 | 隐式锁 | 显式锁 |
获取锁的方式 | 自动 | 需要显式调用 lock() 方法 |
释放锁的方式 | 自动 | 需要显式调用 unlock() 方法 |
可重入性 | 是 | 是 |
公平锁支持 | 否 | 是(通过构造函数可以指定) |
条件变量支持 | 否 | 是(通过 newCondition() 方法) |
获取锁中断 | 否 | 是(通过 lockInterruptibly() 方法) |
超时锁获取 | 否 | 是(通过 tryLock(long time, TimeUnit unit) 方法) |
性能开销 | 较低(JVM 内部优化) | 较高 |
锁的范围 | 代码块或方法 | 灵活,可锁代码块 |
代码复杂度 | 低 | 较高 |
推荐使用场景 | 简单的同步需求 | 复杂的同步需求,如需要公平锁、超时等 |
6. AQS(Abstract Queued Synchronizer,抽象队列同步器)
(1)介绍
AQS是Java 并发包 (java.util.concurrent.locks) 中的一个核心框架,是构建各种锁和同步组件的基础框架。
(2)实现原理
- AQS 使用一个 int 类型的变量 state 表示同步状态。
- AQS 内部维护了一个 FIFO 队列,用于管理获取锁失败的线程。线程在争夺锁失败后会被封装成队列节点(Node)并加入到等待队列中。
- 队列中每个节点包含线程引用和状态标志
(3)常用方法
方法 | 描述 |
---|---|
acquire(int arg) | 独占模式获取锁,如果获取失败则进入等待队列。 |
release(int arg) | 独占模式释放锁,如果释放成功则唤醒等待队列中的下一个节点。 |
acquireShared(int arg) | 共享模式获取锁,如果获取失败则进入等待队列。 |
releaseShared(int arg) | 共享模式释放锁,如果释放成功则唤醒等待队列中的下一个节点。 |
addWaiter(Node mode) | 将当前线程封装成节点并加入等待队列的尾部。 |
unparkSuccessor(Node node) | 唤醒等待队列中的下一个节点。 |
(4)常见实现类
实现类 | 描述 |
---|---|
ReentrantLock | 可重入独占锁。允许线程重复获取同一把锁。 |
ReentrantReadWriteLock | 可重入读写锁。支持多个线程同时读取数据,但写操作是互斥的。 |
Semaphore | 信号量。控制同时访问特定资源的线程数。 |
CountDownLatch | 倒计时闩。一个或多个线程等待其他线程完成操作后再继续执行。 |
CyclicBarrier | 循环栅栏。一组线程相互等待,达到同步点后再继续执行。 |
Phaser | 阶段器。管理多个参与者线程的同步,支持动态注册和注销。 |
StampedLock | 读写锁的改进版本,支持乐观读取和写锁。 |
Condition | 条件变量。与锁配合使用,允许线程等待某个条件变为真。 |
相关文章:

秋招Java后端开发冲刺——并发篇2(JMM与锁机制)
本文对Java的内存管理模型、volatile关键字和锁机制进行详细阐述,包括synchronized关键字、Lock接口及其实现类ReentrantLock、AQS等的实现原理和常见方法。 一、JMM(Java内存模型) 1. 介绍 JMM定义了共享内存中多线程程序读写操作的行为规…...
记录一次Chrome浏览器自动排序ajax请求的JSON数据问题
文章目录 1.前言2. 为什么会这样?3.如何解决? 1.前言 作者作为新人入职的第一天,mentor给了一个维护公司运营平台的小需求,具体需求是根据运营平台的某个管理模块所展示记录的某些字段对展示记录做排序。 第一步: myb…...
【嵌入式——FreeRTOS】任务
【嵌入式——FreeRTOS】任务 任务创建和删除动态方式创建任务静态方式创建任务 删除任务任务切换调度器任务切换流程 任务挂起任务恢复相关API函数 任务创建和删除 动态方式创建任务 任务的任务控制块以及任务的栈空间所需的内存,均由freeRTOS从freeRTOS管理的堆中…...
网关,路由器,交换机
一、网关 (Gateway) 是一种设备,用于连接不同网络,能够转发数据包并翻译协议,允许不同类型的网络通信。网关通常工作在OSI模型的应用层或传输层,提供连接和路由服务。 应用场景例子: 在企业网络中,网关可…...

sublime 3 背景和字体颜色修改
sublime 4 突然抽风,每次打开都显示 “plugin_host-3.3 has exited unexpectedly, some plugin functionality won’t be available until Sublime Text has been restarted” 一直没调好,所以我退回到sublime 3了。下载好了软件没问题,但是一…...

leetcode 403周赛 包含所有1的最小矩形面积||「暴力」
3197. 包含所有 1 的最小矩形面积 II 题目描述: 给你一个二维 二进制 数组 grid。你需要找到 3 个 不重叠、面积 非零 、边在水平方向和竖直方向上的矩形,并且满足 grid 中所有的 1 都在这些矩形的内部。 返回这些矩形面积之和的 最小 可能值。 注意…...

Stable Diffusion web UI 插件
2024.7.3更新,持续更新中 如果需要在linux上自己安装sd,参考:stable diffusion linux安装 插件复制到 /stable-diffusion-webui/extensions 目录下,然后重新启动sd即可 一、插件安装方法 每种插件的安装方法可能略有不同…...
深度学习中的反向传播算法的原理
深度学习中的反向传播算法的原理,以及如何计算梯度 反向传播算法(Backpropagation)是深度学习中最核心的优化技术之一,用于训练神经网络。它基于链式法则,通过从输出层逆向计算误差并逐层传递到输入层来更新模型参数&…...
身处奇瑞看三星:既“开卷“又“起火“,却更难受了
三星"起火" 这几天奇瑞的事情,让大家破防了,纷纷表示国内的就业市场环境普遍恶劣。 那我们转个眼,看看海外企业的情况。 最近一周,三星频频登上新闻,颇有"起火"之势。 在刚步入下半年的 7 月 1 日…...

系统架构设计师教程(清华第2版)<第1章 绪论>解读
系统架构设计师教程 第一章 绪论 1.1 系统架构概述1.1.1 系统架构的定义及发展历程1.1.2 软件架构的常用分类及建模方法1.1.3 软件架构的应用场景1.1.4 软件架构的发展未来1.2 系统架构设计师概述1.2.1 架构设计师的定义、职责和任务1.2.2 架构设计师应具备的专业素质1.3 如何成…...

Vue + Element UI + JSEncrypt实现简单登录页面
安装依赖 npm install jsencrypt --save局部引入 import JSEncrypt from jsencrypt/bin/jsencrypt;登录页面index.vue <template><div class"loginbody"><div class"logindata"><div class"logintext"><h2>Wel…...

从“关注流”到“时间线”,搜狐给内容加信任价值
文 | 螳螂观察 作者 | 易不二 在近日第十六季搜狐新闻马拉松活动中,搜狐新闻APP的“时间线”功能备受瞩目。不仅开幕式现场竖了一块“左手时间线,右手关注流”的路牌,张朝阳也着重强调了“时间线”产品的互动方式:“关注是基础&…...

vscode的一些使用问题
vscode使用技巧 1、快捷键(1)打开命令面板(2)注释(3)删除行(4)上下移动光标(5)光标回退(6)复制行(7)插入空白行…...

爬虫-网页基础
HTML 基本语法 HTML:Hyper Text Markup Language, 超文本标记语言,是计算机语言的一种,由元素构成。 p元素 <p>Web 真好玩!</p> 由三大部分组成 开始标签:一对尖括号中间包裹这元素名称元素内容&#x…...

保存huggingface缓存中AI模型(从本地加载AI模型数据)
在github下拉项目后,首次运行时会下拉一堆模型数据,默认是保存在缓存的,如果你的系统盘空间快满的时候就会被系统清理掉,每次运行又重新下拉一次,特别麻烦。 默认下载的缓存路径如下:C:\Users\用户名\.cache\huggingf…...
wps的xlsm和xltm和xlam格式的文件各有什么区别
文章目录 一、前言二、WPS表格文件格式介绍1. .xlsm 文件格式2. .xltm 文件格式3. .xlam 文件格式 三、总结 一、前言 本文将详细介绍WPS表格中三种常见的文件格式:.xlsm、.xltm、和.xlam,并提供通俗易懂的解释和示例,帮助用户理解它们的区别…...

软件性能测试有哪几种测试方法?专业性能测试报告出具
软件性能测试是指对软件系统在特定负载条件下的性能进行评估和验证的过程,目的是确保软件在正常使用的情况下能够满足用户的要求,并在稳定的性能水平下运行,在软件开发过程中起到了至关重要的作用,可以确保软件产品的质量和可靠性…...
JavaScript语言简介与实战应用:从零开始的编程之旅
JavaScript,一种轻量级的、解释型的、面向对象的脚本语言,自1995年由Netscape公司的Brendan Eich设计以来,迅速成为了Web开发中不可或缺的一部分。它不仅能够为静态网页添加动态效果,还能实现客户端与服务器的交互,如今…...
如何理解synchronized锁升级
在Java中,synchronized 关键字是实现线程同步的一种方式,它涉及到锁的升级和释放的过程。理解synchronized 锁的升级可以分为三个阶段:无锁状态、偏向锁状态和轻量级锁状态。 无锁状态: 当对象被创建时,默认处于无锁状…...
js【最佳实践】遍历数组的八种方法(含数组遍历 API 的对比)for,forEach,for of,map,filter,reduce,every,some
遍历方法返回值使用场景备注副作用for 循环——遍历数组通用可以改变原数组forEach 循环——遍历数组ES5 新增,不支持中断和异步可以改变原数组for of 循环——遍历数组ES6 新增可以改变原数组map格式化后的数组格式化数组的API不会改变原数组filter过滤后的数组过滤…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...

基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...