JAVA进阶-锁

news2026/3/19 5:33:33
1.悲观锁和乐观锁悲观锁在修改数据时一定有别的线程来使用一定会发生并发冲突所以在获取数据的时候会加锁。JAVA中的synchronized和lock都是悲观锁。乐观锁在修改数据时一定没有别的线程来使用所以不会添加锁。但是在更新数据的时候会查看有没有线程修改数据。比如版本号和CAS原理无锁原理实现方式悲观锁数据库层面select * from dual for update;JAVA层面synchronized、ReentrantLock 等独占锁。乐观锁数据库层面UPDATE table SET ...version version 1 where id ? and version ?还有时间戳、CAS。优点悲观锁强一致性写多场景非常安全不会出现并发覆盖问题乐观锁无锁性能极高高并发友好不会阻塞缺点悲观锁性能差容易阻塞、死锁高并发下吞吐量极低乐观锁冲突多时重试成本高业务代码要处理重试流程悲观锁上锁读取 / 修改释放锁乐观锁读取数据拿到 version业务处理更新时带上 version 做条件影响行数 0 → 冲突重试 / 报错使用场景悲观锁写多读少金融、库存扣减、订单支付等强一致场景乐观锁读多写少商品详情、点赞、统计、日志等1.1 CAS全面解析乐观锁的核心实现CAS是 Compare And Swap(比较并交换)的缩写是实现乐观锁的核心技术也是CPU层面提供的原子指令非JAVA语法可以理解为我认为内存里的值是A现在要改成B先检查内存里是不是真的是A如果是就改成B不是就不改。1.1.1 CAS核心思想3个核心值CAS 操作依赖3个关键参数1.内存地址V要操作的变量在内存中的位置。2.预期值A认为当前内存中应该有的值。3.新值B想要更新成的新值。执行逻辑一步到位的原子操作if (内存值 V 预期值 A) { 将内存值 V 改为 新值 B 返回 成功true } else { 不做任何修改 返回 失败false }关键整个“比较 交换” 是原子性的CPU保证不会被线程打断所以不需要加锁也能保证并发安全。1.1.2 CAS 通俗举例比如你和朋友同时给一个共享的计数器 count 加1 初始值 01. 读取到 count 0 预期值 A 0准备改成 1 新值 B 12.CAS检查内存中 count 是不是 0如果是 直接改成1返回成功如果已经先把 count 改成 1 检查失败返回失败 (需要重新读取最新值再重试)1.1.3 JAVA中的CAS实战JAVA 本身不能直接调用CPU指令但JDK 提供了java.util.concurrent.atomic 包封装了CAS操作最常用的是AtomicIntegerimport java.util.concurrent.atomic.AtomicInteger; public class CASDemo { public static void main(String[] args) { // 初始化值为 0 AtomicInteger count new AtomicInteger(0); // 核心 CAS 操作compareAndSet(预期值, 新值) boolean result1 count.compareAndSet(0, 1); System.out.println(第一次更新 result1); // true更新成功count1 // 再次用 0 作为预期值更新此时内存值是 1 boolean result2 count.compareAndSet(0, 2); System.out.println(第二次更新 result2); // false更新失败 // 用最新值 1 作为预期值更新 boolean result3 count.compareAndSet(1, 2); System.out.println(第三次更新 result3); // true更新成功count2 } }常用简化方法 底层还是CASAtomicInteger提供了更易用的方法 如incrementAndGet()底层都是 CAS 自旋重试// 等价于 count但线程安全CAS 自旋重试直到成功 int newCount count.incrementAndGet(); System.out.println(newCount); // 31.1.4 CAS的优点 缺点优点1.无锁不需要加 synchronized 或 Lock 避免线程阻塞和上下文切换性能极高。2.原子性 CPU 级别的原子操作比锁更轻量。3.并发友好高并发下吞吐量远高于悲观锁。缺点1.ABA问题最核心问题变量从A - B - A,CAS 检查时发现是A会误以为没被修改但实际已经被改过。解决JDK 提供 AtomicStampedReference 加版本号/时间戳类似乐观锁的版本号机制2.自旋开销如果并发冲突频繁CAS会一直重试自旋消耗 CPU 资源3.只能操作单个变量CAS只能保证单个变量的原子性无法解决多个变量的原子操作 需用锁或AtomicInteger 封装对象2.自旋锁和自适应自旋锁2.1 自旋锁基于CAS实现的一种非阻塞锁,核心思想是线程获取锁失败时不直接阻塞而是再循环中不断重试自旋直到成功获取锁。而自适应自旋锁是自旋锁的优化版本会根据“过往经验”动态调整自旋次数是JVM中 Synchronized 锁升级的重要环节。2.1.1 为什么需要自旋锁传统悲观锁如 synchronized 早期实现获取锁失败时线程会被挂起从运行态到阻塞态这涉及操作系统的上下文切换开销很大。但实际场景中很多锁的持有时间极短比如几纳秒线程挂起再唤醒的时间可能比锁占用的时间还长。此时自旋重试比阻塞更高效——相当于“在门口等几秒而不是直接回家阻塞”2.1.2 自旋锁核心原理1.基于CAS原子操作实现线程自旋时不断用 CAS 检查锁是否被释放。2.自旋过程中线程处于运行态不是阻塞态,只是空耗CPU直到获取锁或自旋结束。2.1.3 JAVA 手动实现自旋锁import java.util.concurrent.atomic.AtomicBoolean; // 自旋锁核心CAS 循环重试 public class SpinLock { // 用 AtomicBoolean 标记锁状态false未锁定true已锁定 private AtomicBoolean locked new AtomicBoolean(false); // 获取锁自旋直到 CAS 成功 public void lock() { // CAS 尝试将 locked 从 false 改为 true while (!locked.compareAndSet(false, true)) { // 自旋空循环直到成功 // 注意实际开发中可加 Thread.yield() 让出CPU减少空耗 } } // 释放锁直接置为 false public void unlock() { locked.set(false); } // 测试 public static void main(String[] args) { SpinLock spinLock new SpinLock(); // 线程1获取锁 new Thread(() - { spinLock.lock(); try { System.out.println(线程1获取锁执行任务); Thread.sleep(100); // 模拟短时间持有锁 } catch (InterruptedException e) { e.printStackTrace(); } finally { spinLock.unlock(); System.out.println(线程1释放锁); } }).start(); // 线程2自旋等待锁 new Thread(() - { spinLock.lock(); try { System.out.println(线程2获取锁执行任务); } finally { spinLock.unlock(); System.out.println(线程2释放锁); } }).start(); } }执行结果线程1获取锁执行任务 线程1释放锁 线程2获取锁执行任务 线程2释放锁线程 2 在获取锁失败时会在while循环中自旋直到线程 1 释放锁后 CAS 成功。2.1.4 自旋锁的优缺点优点缺点无上下文切换开销短持有时间锁性能极高自旋空耗 CPU高冲突时导致 CPU 使用率飙升非阻塞线程不会被挂起自旋次数固定无法适配不同场景比如锁持有时间长时自旋纯浪费实现简单基于 CAS 即可可能导致 活锁多个线程同时自旋重试互相抢不到锁2.2 自适应自旋锁自适应自旋锁是 JDK 1.6 为synchronized引入的优化解决了普通自旋锁 自旋次数固定 的问题。2.2.1 核心原理自旋次数不固定而是根据「上一次获取该锁的自旋情况」动态调整如果上一次自旋成功获取了锁说明该锁的持有时间短本次自旋次数会增加比如从 10 次→20 次认为这次也大概率能快速拿到锁。如果上一次自旋失败了说明该锁的持有时间长本次自旋次数会减少甚至直接不自旋直接进入阻塞避免浪费 CPU。如果自旋次数超过阈值自动切换为阻塞传统悲观锁方式。2.2.2 自适应自旋锁的优势智能适配场景不用手动设置自旋次数JVM 自动根据历史经验调整。平衡性能与资源短锁自旋省开销长锁不自旋省 CPU。是 synchronized 锁升级的关键synchronized的锁升级流程无锁 → 偏向锁 → 轻量级锁自适应自旋 → 重量级锁。2.3 自旋锁 VS 自适应自旋锁 对比特性普通自旋锁自适应自旋锁自旋次数固定比如 10 次 / 20 次动态调整基于历史经验灵活性低高CPU 利用率高冲突时易空耗更合理减少无效自旋适用场景锁持有时间固定的简单场景复杂并发场景如 JVM 内部典型应用手动实现的简单自旋锁JDK 1.6 的 synchronized2.4 实战注意事项自旋锁适合短锁只有锁持有时间极短微秒 / 纳秒级时自旋才有意义如果锁持有时间长自旋会浪费大量 CPU。避免自旋次数过多手动实现自旋锁时可设置最大自旋次数比如 1000 次超过则放弃自旋转为阻塞。JVM 对自旋的控制可通过 JVM 参数调整自旋行为-XX:PreBlockSpin设置普通自旋锁的最大自旋次数JDK 1.6 后该参数失效自适应自旋接管。-XX:-UseSpinning关闭自旋锁所有失败直接阻塞。3.无锁、偏向锁、轻量级锁、重量级锁这些锁状态是 Java 为了优化synchronized关键字性能而设计的锁升级机制核心思想是在不同并发程度下使用成本最低的锁策略从无锁到重量级锁是一个单向升级的过程不会降级。3.1 无锁定义没有任何线程竞争资源所有线程都可以自由访问共享资源。本质不是真正的“锁”而是通过 CAS 乐观锁机制保证操作原子性避免阻塞。适合场景几乎无并发竞争的场景单线程操作。import java.util.concurrent.atomic.AtomicInteger; public class NoLockDemo { // 原子类底层通过 CAS 实现无锁操作 private static AtomicInteger count new AtomicInteger(0); public static void main(String[] args) { // 多线程自增但底层用 CAS 无锁实现 for (int i 0; i 5; i) { new Thread(() - { count.incrementAndGet(); // CAS 自增无锁竞争 System.out.println(Thread.currentThread().getName() : count.get()); }).start(); } } }3.2 偏向锁定义当只有一个线程反复获取同一把锁时JVM 会让该线程“偏向”这把锁——锁对象会记录当前线程的ID后续该线程获取锁时无需任何竞争操作直接返回。核心优化消除无竞争场景下的锁获取/释放开销比轻量级锁更高效。适用场景单一线程重复获取锁的场景单线程循环加锁。触发升级当有第二个线程尝试获取锁时偏向锁会升级为轻量级锁。关键特点默认开启JDK 1.6,可通过 -XX-UseBiasedLocking 关闭。偏向锁的撤销有一定延迟约4秒避免频繁撤销影响性能。3.3 轻量级锁定义当多个线程交替获取同一把锁无激烈竞争时JVM会使用轻量级锁——线程通过CAS操作尝试获取锁失败时不会立即阻塞而是自旋循环重试。核心优化避免线程阻塞/唤醒的内核态切换开销 自旋是用户态操作。适用场景多线程交替竞争锁短时间、低冲突。触发升级当自旋次数达到阈值默认 10 次或自旋线程数超过 CPU 核心数的一半时轻量级锁升级为重量级锁。3.4 重量级锁定义依赖操作系统的互斥量Mutex实现线程获取锁失败时会被挂起进入阻塞状态直到锁释放后被唤醒。核心特点开销大涉及用户态—— 内核态切换线程阻塞/唤醒成本高。适用场景高并发、长持有锁的场景自旋已无意义。synchronized 触发重量级锁public class HeavyLockDemo { private static final Object lock new Object(); public static void main(String[] args) { // 两个线程同时竞争锁最终触发重量级锁 new Thread(() - { synchronized (lock) { try { Thread.sleep(1000); // 长时间持有锁触发锁升级 System.out.println(线程1持有锁); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(() - { synchronized (lock) { System.out.println(线程2获取锁); } }).start(); } }锁状态升级流程单向3.5 总结无锁基于 CAS 实现无竞争时最优无锁开销偏向锁单线程专属消除无竞争下的锁操作开销有新线程竞争时升级轻量级锁多线程交替竞争自旋 CAS 获取锁自旋失败则升级为重量级锁重量级锁依赖操作系统互斥量线程阻塞 / 唤醒开销大适用于高并发长持有场景。核心逻辑JVM 从 “乐观” 到 “悲观” 逐步调整锁策略尽可能降低锁的使用成本。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…