当前位置: 首页 > news >正文

深入学习并发编程中的 synchronized

文章目录

  • 并发编程中的三个问题
    • 可见性
    • 原子性
    • 有序性
  • 了解Java内存模型JMM
  • synchronized 保证三大特性
    • synchronized 保证原子性
    • synchronized 保证可见性
    • synchronized 保证有序性
  • synchronized 的特性
    • 可重入特性
    • 不可中断特性
  • 通过反汇编学习synchronized原理
    • 当修饰代码块时
    • 当修饰方法时
  • 通过JVM源码学习synchronized
    • monitor 监视器锁
    • monitor竞争
    • monitor等待
    • monitor释放

并发编程中的三个问题

可见性

是指一个线程对共享变量进行修改,另一个先立即得到修改后的最新值

演示可见性问题:

在这里插入图片描述

实际效果:线程 1 并没有退出循环,说线程二修改了flag的值,而线程一并没有立即得到修改后的值。

在这里插入图片描述

原子性

原子性(Atomicity):在一次或多次操作中,要么所有的操作都执行并且不会受其他因素干扰而中断,要么所有的操作都不执行。

演示原子性问题:

在这里插入图片描述

执行几次后的实际结果:

在这里插入图片描述

证明:

number++ 实际上是一个复合操作(且是非原子性的),它包含三个步骤:

  1. 读取 number 的当前值。
  2. 将读取的值加1。
  3. 将结果写回到 number

再通过反汇编说明number++的问题:

javap -p -v .\AtomicityDemo

其中,对于number++ 而言(number 为静态变量),实际会产生如下的 JVM 字节码指令:

在这里插入图片描述

number++ 一共由于4条命令构成

  • getstatic:获取到当前 number 的值
  • iconst_1:准备常量 1
  • iadd:当前值与 iconst_1 做加法
  • putstatic:将结果赋值给 number

以上多条指令在一个线程的情况下是不会出问题的,但是在多线程环境下就可能会出现问题。比如一个线程在执行 13: iadd 时,另一个线程又执行 9: getstatic。会导致两次 number++,实际上只加了1。

有序性

是指程序代码在执行过程中的先后顺序,由于Java在编译期以及运行期的优化,导致了代码的执行顺序未必就是开发者编写代码时的顺序。

举个例子🌰:

int a = 1; // 操作1
int b = 2; // 操作2// 重排序后
int b = 2; // 操作2
int a = 1; // 操作1

出现可见性问题的两个前提:至少有两个线程、有个共享变量

public class Ordering {private int num = 0;private boolean flag = false;private int x;public void action1() {if (flag) {x = num + num;} else {x = 1;}// x的可能结果System.out.println(x);}public void action2() {num = 2;flag = true;}
}

x的可能结果:

  • 结果1:线程1执行 action1(),此时 flag=false,x的结果为1
  • 结果2:线程2先执行了 action2(),线程1再执行 action1(),此时 flag=true, num=2,x的结果为4
  • 结果3:java在编译和运行时会对代码进行优化,action2()的执行顺序变成了如下,此时线程2更改flag值之后,CPU切换到线程1执行,num为初始化的值0,x的结果为0
public void action2() { // 因为第2行和第3行代码并没有逻辑关系 Java在编译期以及运行期的优化 可能会将其改变顺序flag = true;num = 2;
}

上面的结果3就是有序性产出的并发问题

了解Java内存模型JMM

Java内存模型(Java Memory Model, JMM)是Java虚拟机(JVM)定义的一种规范,用于描述多线程程序中变量(包括实例字段、静态字段和数组元素)如何在内存中存储和传递的规则。规范了线程何时会从主内存中读取数据、何时会把数据写回主内存。

JMM 抽象了线程和主内存之间的关系,就比如说线程之间的共享变量必须存储在主内存中。

JMM 的核心目标是确保多线程环境下的可见性、有序性和原子性,从而避免由于硬件和编译器优化带来的不一致问题。

  • 可见性:确保一个线程对变量的修改,能及时被其他线程看到。关键字 volatile 就是用来保证可见性的,它强制线程每次读写时都直接从主内存中获取最新值。
  • 有序性:指线程执行操作的顺序。JMM允许某些指令重排序以提高性能,但会保证线程内的操作顺序不会被破坏,并通过 happens-before 关系保证跨线程的有序性。
  • 原子性:是指操作不可分割,线程不会在执行过程中被中断。例如,synchronized 关键字能确保方法或代码块的原子性。
    在这里插入图片描述

从 JMM 了解可见性:线程 1 将 a 的值拷贝一份并修改了 a 的值,然后同步给主内存的值,而线程 2 一直用的是副本的值,并不知道主内存的值已被修改了,所以线程 1 修改的值,对于线程 2 来说是不可见的。

synchronized 保证三大特性

synchronized能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。

synchronized (锁对象) {
// 受保护资源;
}

synchronized 保证原子性

synchronized保证原子性的原理,synchronized保证只有一个线程拿到锁,能够进入同步代码块。

案例演示:5个线程各执行1000次 i++;

public class Test01Atomicity {private static int number = 0;public static void main(String[] args) throws InterruptedException {Runnable increment = () -> {for (int i = 0; i < 1000; i++) {synchronized (Test01Atomicity.class) {number++;}}};ArrayList<Thread> ts = new ArrayList<>();for (int i = 0; i < 50; i++) {Thread t = new Thread(increment);t.start();ts.add(t);}for (Thread t : ts) {t.join();}System.out.println("number = " + number);}
}

对number++;增加同步代码块后,保证同一时间只有一个线程操number++;。就不会出现安全问题。

synchronized 保证可见性

案例演示:一个线程根据boolean类型的标记flag, while循环,另一个线程改变这个flag变量的值,另一个线程并不会停止循环。

public class Test01Visibility {// 多个线程都会访问的数据,我们称为线程的共享数据private static boolean run = true;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while (run) {// 增加对象共享数据的打印,println是同步方法System.out.println("run = " + run);}});t1.start();Thread.sleep(1000);Thread t2 = new Thread(() -> {run = false;System.out.println("时间到,线程2设置为false");});t2.start();}
}

点进去println,本质还是加了 synchronized 关键字

在这里插入图片描述

synchronized 保证有序性

为什么要重排序:

为了提高程序的执行效率,编辑器和cpu会对程序中的代码进行重排序

as-if-serial语义:

as-if-serial语义的意思是:不管编译器和CPU如何重排序,必须保证在单线程情况下程序的结果是正确的。 以下数据有依赖关系,不能重排序。

  • 写后读

    int a = 1;
    int b = a;
    
  • 写后写

    int a = 1;
    int a = 2;
    
  • 读后写

    int a = 1;
    int b = a;
    int a = 2;
    

synchronized 后,虽然进行了重排序,保证只有一个线程会进入同步代码块,也能保证有序性。

synchronized 保证有序性的原理,加s ynchronized 后,依然会发生重排序,只不过,我们有同步代码块,可以保证只有一个线程执行同步代码中的代码。保证有序性。

synchronized 的特性

可重入特性

synchronized内部维护了一个计数器( recursions 变量),记录线程是第几次获取锁,在执行完同步代码块时,计数器的数量会 - 1 ,直到计时器的数量为 0,就释放这个锁。

在这里插入图片描述

好处:1)可以避免死锁 2)可以让我们更好的来封装代码

不可中断特性

不可中断特性:一个线程获得锁后,另一个线程想要获得锁,必须处于阻塞或等待状态,如果第一个线程不释放锁,第二个线程会一直阻塞或等待,不可被中断。

public class Demo02_Uninterruptible {private static final Object obj = new Object();public static void main(String[] args) throws InterruptedException {// 1.定义一个RunnableRunnable run = () -> {// 2.在Runnable定义同步代码块synchronized (obj) {String name = Thread.currentThread().getName();System.out.println(name + "进入同步代码块");// 保证不退出同步代码块try {Thread.sleep(888888);} catch (InterruptedException e) {e.printStackTrace();}}};// 3.先开启一个线程来执行同步代码块Thread t1 = new Thread(run);t1.start();Thread.sleep(1000);// 4.后开启一个线程来执行同步代码块(阻塞状态)Thread t2 = new Thread(run);t2.start();// 5.停止第二个线程System.out.println("停止线程前");t2.interrupt();System.out.println("停止线程后");System.out.println(t1.getState());System.out.println(t2.getState());}
}

执行结果:

t1 线程不释放锁,t2 线程会一直阻塞或等待,既不可被中断。

在这里插入图片描述

通过反汇编学习synchronized原理

它的实现原理依赖与 JVM 中的 Monitor(监视器锁)和对象头(Object Header)。

当修饰代码块时

会在代码块的前后插入monitorentermonitorexit字节码指令,可以把monitorenter理解为加锁,monitorexit理解为解锁。

第二次出现 monitorexit 可以理解为出现异常的情况也需要解锁。

在这里插入图片描述

monitorenter:

  • synchronized 的锁对象会关联一个 monitor,这个 monitor 不是我们主动创建的,是 JVM 的线程执行到这个同步代码块,发现锁对象没有monitor 就会创建 monitor,monitor 内部有两个重要的成员变量owner: 拥有 这把锁的线程,recursions 会记录线程拥有锁的次数,当一个线程拥有 monitor 后其他线程只能等待。

monitorexit:

  • 能执行 monitorexit 指令的线程一定是拥有当前对象的 monitor 的所有权的线程。
  • 执行 monitorexit 时会将 monitor 的进入数减 1。当 monitor 的进入数减为 0 时,当前线程退出 monitor,不再拥有 monitor 的所有权,此时其他被这个 monitor 阻塞的线程可以尝试去获取这个 monitor的所有权。

当修饰方法时

在这里插入图片描述

方法的常量池会添加一个 ACC_SYNCHRONIZED 标识,当某个线程访问这个方法时会检查是否有ACC_SYNCHRONIZED标识,若有则需要获取监视器锁才可以执行方法,此时就保证了方法的同步。

通过JVM源码学习synchronized

monitor 监视器锁

在 HotSpot 虚拟机中 monitor 是通过 ObjectMonitor 实现的,底层是 c++ 实现的,其数据结构和解释如下:

在这里插入图片描述

monitor竞争

  1. 通过CAS尝试把 monitor 的 owner 字段设置为当前线程。
  2. 如果设置之前的 owner 指向当前线程,说明当前线程再次进入monitor,即重入锁,执行 recursions ++ ,记录重入的次数。
  3. 如果当前线程是第一次进入该 monitor,设置 recursions 为 1,_owner 为当前线程,该线程成功获 得锁并返回。
  4. 如果获取锁失败,则等待锁的释放。

monitor等待

  1. 当前线程被封装成ObjectWaiter对象node,状态设置成ObjectWaiter::TS_CXQ。
  2. 在for循环中,通过 CAS 把 node 节点 push 到 _cxq 列表中,同一时刻可能有多个线程把自己的 node 节点 push 到_cxq列表中。
  3. node节点push到_cxq列表之后,通过自旋尝试获取锁,如果还是没有获取到锁,则通过park将当 前线程挂起,等待被唤醒。
  4. 当该线程被唤醒时,会从挂起的点继续执行,通过ObjectMonitor::TryLock 尝试获取锁。

monitor释放

当某个持有锁的线程执行完同步代码块时,会进行锁的释放,给其它线程机会执行同步代码,在 HotSpot中,通过退出monitor的方式实现锁的释放,并通知被阻塞的线程。

  1. 退出同步代码块时会让_recursions 减 1,当 recursions 的值减为0时,说明线程释放了锁。
  2. 根据不同的策略(由QMode指定),从cxq或EntryList中获取头节点,通过 ObjectMonitor::ExitEpilog 方法唤醒该节点封装的线程,唤醒操作最终由unpark完成。

相关文章:

深入学习并发编程中的 synchronized

文章目录 并发编程中的三个问题可见性原子性有序性 了解Java内存模型JMMsynchronized 保证三大特性synchronized 保证原子性synchronized 保证可见性synchronized 保证有序性 synchronized 的特性可重入特性不可中断特性 通过反汇编学习synchronized原理当修饰代码块时当修饰方…...

AMD R9-9950X相比较I9-14900K有哪些提升

AMD R9-9950X相比较I9-14900K有哪些提升&#xff1f;在处理器领域&#xff0c;AMD与英特尔的竞争从未停歇&#xff0c;每一次新品发布都引发业界的高度关注。近日&#xff0c;AMD推出了其新一代桌面级旗舰处理器——Ryzen 9 9950X&#xff08;简称R9-9950X&#xff09;&#xf…...

计算机毕业设计 基于Python的个性化旅游线路推荐系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…...

总结:Flink之DataStream各API介绍

一、介绍 本文主要是详细介绍 DataStream<T> 类中的各个方法,并给出它们的使用场景。 二、基本方法 getId(): 作用:返回转换操作的唯一标识符。场景:当需要调试或日志记录时,有时候需要知道操作的 ID。getParallelism(): 作用:获取流的并行度。场景:在优化作业时…...

设计一个日志管理系统,支持多级别日志记录

设计一个日志管理系统,支持多级别日志记录 作为一名Python程序软件专家,我经常被问到关于日志管理系统的设计和实现。今天,我将分享一篇关于设计一个日志管理系统,支持多级别日志记录的博文,希望能够帮助大家更好地理解和使用Python语言。 日志管理系统的需求 在软件开…...

Javascript动态规划算法

JavaScript中的动态规划&#xff08;Dynamic Programming&#xff0c;简称DP&#xff09;是一种通过把原问题分解为相对简单的子问题的方式来求解复杂问题的方法。它主要致力于将“合适”的问题拆分成更小的子目标&#xff0c;并通过建立状态转移方程、缓存并复用以往结果以及按…...

Java 循环里怎么删除元素才安全

首先 在 Java 中&#xff0c;当你在循环中遍历集合时&#xff0c;直接删除元素可能会引发 ConcurrentModificationException。为了安全地删除元素&#xff0c;推荐使用 Iterator 来进行删除操作。 以下是使用 Iterator 删除元素的常见模式&#xff1a; import java.util.Arr…...

LabVIEW晶体振荡器自动化测试系统

基于LabVIEW平台的晶体振荡器自动化测试系统解决了传统手工测试晶体振荡器繁琐且易出错的问题。该系统通过高度自动化的测试流程&#xff0c;提高了测试效率和精度&#xff0c;实现了数据的自动采集与处理&#xff0c;适用于电子、通信等领域的晶振测试需求。 项目背景与意义 …...

3.6.xx版本SpringBoot创建基于Swagger接口文档

介绍 基于Swagger构建的JavaAPI文档工具&#xff0c;实现后端功能的测试&#xff0c;并撰写API接口文档。 方法 pom.xml中引入依赖,要注意的是&#xff0c;本依赖使用的SpringBoot版本为3.6.xx <!--Knife4j--><dependency><groupId>com.github.xiaoymin<…...

Oracle 12201非PDBS模式单机部署(静默安装)

一、创建Oracle数据库的用户 groupadd oinstall groupadd dba groupadd asmadmin groupadd asmdba useradd -g oinstall -G dba,asmdba oracle -d /home/oracle passwd oracle二、配置Linux 服务器参数 cat /home/oracle/.bash_profile export ORACLE_HOSTNAMEH_orcle01 expo…...

Python 源码编译安装详解:跨平台指南及完整步骤解析

Python 源码编译安装详解&#xff1a;跨平台指南及完整步骤解析 文章目录 Python 源码编译安装详解&#xff1a;跨平台指南及完整步骤解析一 准备工作1&#xff09;Ubuntu/Debian2&#xff09;CentOS/RHEL3&#xff09;macOS 二 下载 Python 源码三 编译与安装1&#xff09;解压…...

MQTT vs HTTP:谁更适合物联网?

前言 随着物联网&#xff08;IoT&#xff09;技术的飞速发展中&#xff0c;其应用规模和使用场景正在持续扩大&#xff0c;但它关键的流程仍然是围绕数据传输来进行的&#xff0c;因此设备通信协议选择至关重要。 作为两种主要的通信协议&#xff0c;MQTT 协议和 HTTP 协议各…...

小北的技术博客:探索华为昇腾CANN训练营与AI技术创新——Ascend C算子开发能力认证考试(初级)

前言 哈喽哈喽友友们,这里是zyll~(小北)智慧龙阁的创始人及核心技术开发者。在技术的广阔天地里,我专注于大数据与全栈开发,并致力于成为这一领域的新锐力量。通过智慧龙阁这个平台,我期望能与大家分享我的技术心得,共同探索技术的无限可能。 Ascend C编程:小北的技术…...

鸿蒙next开发者第一课02.DevEcoStudio的使用-习题

【习题】DevEco Studio的使用 通过/及格分80/ 满分100 判断题 1. 如果代码中涉及到一些网络、数据库、传感器等功能的开发&#xff0c;均可使用预览器进行预览。F 正确(True)错误(False) 预览器不能进行传感器等特殊功能的开发,需要使用真机开发 2. module.json5文件中的…...

【vue】监听table水平滚动条切换tab后还原位置

有个需求就是切换tab后&#xff0c;原先的table水平滚动条要还原位置&#xff08;如下图&#xff09;&#xff0c;先说下思路&#xff0c;大致就是 切出页面时 把滚动距离保存到Storage 中&#xff0c;切回来时在恢复 直接上代码 首先table ref指定一下ref"jtable" …...

C#使用PdfSharp生成PDF文件实例详解

许多项目开发中需要生成PDF, 常规办法使用官方提供的Microsoft.Office.Interop.Worddll插件,但是这种方法需要完全安装OFFICE,另外版本不一致还会出现很多错误。一般不推荐使用。 下面介绍几种巧妙的用法,定能事半功倍。 本文使用PDFsharp完成功能。 PDFsharp一款开源的…...

【软件系统架构设计师-案例-1】架构风格

1. 请用200字以内说明系统可靠性的定义及包含的4个子特性&#xff0c;并简要指出提高系统可靠性一般采用哪些技术&#xff1f; &#xff08;1&#xff09;可靠性定义&#xff1a;系统在规定的时间或环境条件下&#xff0c;完成规定功能的能力&#xff0c;就是系统无故障运行的…...

神经网络整体架构

文章目录 1.输入层Input2.卷积层Conv3.激活函数层(一)Sigmoid 函数(二)Tanh 函数(三)修正线性单元ReLU(四)Leaky ReLU函数(带泄露的Relu)(五)参数化ReLU 4.池化层POOL5.全连接层FC6.输出层Output 用全连接神经网络处理大尺寸图像具有三个明显的缺点&#xff1a; ①将图像展开为…...

山西农业大学20241010

02-JAVASCRIPT 一.JS基础语法1. 数据类型转换1.1 隐式转换1.2 强制转换 2. 运算符 二.JS语句1. 条件语句2. 循环语句 三.函数(方法)1. 声明函数的第一种方法2. 声明函数的第二种方法3. 声明函数的第三种方法 四.对象1. 对象的创建 -- 字面量2. 访问对象的属性3. 内置构造函数以…...

小北的技术博客:探索华为昇腾CANN训练营与AI技术创新——Ascend C算子开发能力认证考试(中级)

前言 哈喽哈喽,这里是zyll~,北浊.(大家可以亲切的呼唤我叫小北)智慧龙阁的创始人,一个在大数据和全站领域不断深耕的技术创作者。今天,我想和大家分享一些关于华为昇腾CANN训练营以及AI技术创新的最新资讯和实践经验~(初级证书还没拿到的小伙伴,可以先参考小北的这篇技术…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...