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

【手撕JAVA多线程:2.线程安全】 2.1.JVM层面的线程安全保证

目录

概述

happen-before和as-if-serial特性

Synchronized

实现

代码示例

volatile

实现

代码示例


概述

本文其实就是讲JMM相关内容,但是由于是想精炼JAVA多线程相关内容,所以不会铺开讲细节,细节前面有文章讲过:

【JAVA多线程】JMM,成体系聊一下JAVA线程安全问题_从jmm解释线程安全问题-CSDN博客

本文,包括本系列是想将整个JAVA多线程的内容精炼出来,以帮助大家形成成体系且精炼的一个认知,所以讲究只讲绝对的精华,要展开之前都有对应的文章。好的开始!

再重复一遍,JAVA多线程的核心内容:

  • 线程操作

  • 线程安全

  • 线程编排

本文将聊一下线程安全部分,要聊JAVA的线程安全,要先知道JAVA线程安全问题的:

  • 不可见性,一条线程对数据做了修改还没从cache中刷回内存中,另外的线程就去读取了数据,那么前一条线程做的数据修改对后面去读取数据的线程来说就是不可见的,从而造成了数据的不同步。

  • 指令重排序,操作系统为了保证程序执行的高效,有时候会对程序中的指令进行重排序,这种重排序可能会造成多线程间执行结果不同,造成数据不一致的线程安全问题。

JAVA提供了两个层面的线程安全的保证:

  • JVM层的保证:Synchronized、volatile

  • 留给开发者的更灵活控制:Lock,Lock底层其实就是用的volatile+CAS,相当于是JDK封装了一个线程安全的工具类给开发者用,免得开发者从0开始造轮子罢了。

happen-before和as-if-serial特性

前面聊了多线程环境下,造成线程安全问题的两大原因是不可见性和指令重排序。我们知道从逻辑上来说要实现一些核心诉求,就要保证实现一些特性,比如数据库为了实现事务,就要保证呈现出ACID的特性。保证多线程环境下的线程安全也是,只要实现as-if-serial、happen-before两个特性即可。

  • happen-before,用来保证可见性,A happen-before B, 则A的执行结果必须对B可见。由Synchronized关键字来保证。

  • as-if-serial,用来保证可见性和指令不被重排序,由volatile关键字来保证。

特别注意:Synchronized和volatile虽说都可以保证可见性,但是两者是没办法相互替代的,Synchronized是用来保证同一时间只有单一线程持有资源的,volatile是用来保证volatile修饰的变量的读写操作前后的那些操作不会被重排序,用来保证操作的有序性。

Synchronized

实现

Synchronized用来修饰方法、变量、一块代码块。修饰方法或者一块代码块的时候用来制造出一块“临界区”(操作系统概论中的概念,即同一时间只允许一条线程进入的区域),修饰一个变量的时候,用来制造出一个临界资源(操作系统概论中的概念,即同一时间只允许一条线程持有的资源)

Synchronized是利用对象的Mark Word来实现的,如果Synchronized修饰的是变量用的就是变量的对象的对象头里面的Mark Word,如果Synchronized用来制造一个同步块利用的就是被持有的对象的对象头的Mark Word,如果Synchronized修饰的是方法,利用的就是this对象的对象头的Mark Word。

具体的实现以及经典的锁升级过程,看上一篇文章:

【手撕JAVA多线程】1.从设计初衷去看JAVA的线程操作-CSDN博客

这里唯一要拿出来单独说的是synchronized并不是当持有资源的线程执行完就唤醒其他线程去立马争抢资源,如果是持有资源的线程执行完就唤醒其他线程去立马争抢资源,还是会存在数据没有回写的可能性,synchronized其底层严格的保证可见性,在进入和退出的时候做了严格的内存同步:

  1. 退出 synchronized 块时(monitorexit 指令)

    • 当前线程的所有修改(包括缓存中的脏数据)必须写回主内存(相当于 volatile 写)。

  2. 进入 synchronized 块时(monitorenter 指令)

    • 线程必须从主内存重新加载变量(相当于 volatile 读)。

代码示例

Synchronized和volatile虽说都可以保证可见性,但是两者是没办法相互替代的,Synchronized是用来保证同一时间只有单一线程持有资源的,volatile是用来保证volatile修饰的变量的读写操作前后的那些操作不会被重排序,用来保证操作的有序性。

场景:多个线程同时修改同一个计数器,保证最终结果正确。

public class SynchronizedExample {private int count = 0;
​// synchronized 方法,保证原子性和可见性public synchronized void increment() {count++;}
​public static void main(String[] args) throws InterruptedException {SynchronizedExample example = new SynchronizedExample();
​// 创建两个线程,每个线程对 count 累加 1000 次Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {example.increment();}});
​Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {example.increment();}});
​t1.start();t2.start();
​t1.join(); // 等待 t1 完成t2.join(); // 等待 t2 完成
​System.out.println("Final count: " + example.count); // 正确输出 2000}
}

volatile

实现

volatile,JAVA虚拟机提供的最轻量级的同步机制。其通过实现“缓存一致性协议”和“内存屏障”,保证了happen-before和强制禁止了指令重排序。

  • 缓存一致性协议 保证工作内存(缓存)中的数据和主内存(内存)中的数据的一致性,即一旦工作内存中的数据有变,马上刷新回主内存。 其底层实现是CPU的嗅探机制,所有CPU都盯住总线,监听总线中的数据变化,一旦工作内存中存 在的数据在总线中出现了assign操作,会立即让工作内存中的相应值失效,从而重新从主内存中去读取值。

  • 不同的CPU有不同的缓存一致性协议。 内存屏障用于禁止指令重排序, 具体的实现是在需要禁止重排序的两条代码(指令)之间插入一个标志,标 识标志两边的代码(指令)禁止重排序。这个标志是汇编级别的。

代码示例

Synchronized和volatile虽说都可以保证可见性,但是两者是没办法相互替代的,Synchronized是用来保证同一时间只有单一线程持有资源的,volatile是用来保证volatile修饰的变量的读写操作前后的那些操作不会被重排序,用来保证操作的有序性,是无法保证同一时间只有单一线程持有资源的。

场景:一个线程修改标志位,另一个线程读取标志位并退出循环。

volatile用来保证在flag = true;前后的指令不会被重排序

public class VolatileExample {private volatile boolean flag = false; // 使用 volatile 保证可见性
​public void start() {// 线程1:1秒后修改 flagnew Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}flag = true; // 修改 flagSystem.out.println("Flag set to true");}).start();
​// 线程2:循环检测 flag,直到 flag=true 才退出new Thread(() -> {while (!flag) {// 空循环,等待 flag 变化}System.out.println("Flag detected as true, exiting...");}).start();}
​public static void main(String[] args) {VolatileExample example = new VolatileExample();example.start();}
}

http://www.dtcms.com/a/346691.html

相关文章:

  • 硬件-时钟学习DAY5——石英晶体负载电容设计全解析
  • Adobe Acrobat 创建和分发交互式 PDF 表单
  • lanczso算法中的额外正交化代码解释
  • Java性能优化实战(六):缓存策略的3大核心优化方向
  • 新手向:异步编程入门asyncio最佳实践
  • PyTorch生成式人工智能——VQ-VAE详解与实现
  • chapter06_应用上下文与门面模式
  • pcie实现虚拟串口
  • k8s之 Pod 资源管理与 QoS
  • 深入理解 C++ SFINAE:从编译技巧到现代元编程的演进
  • rust语言 (1.88) egui (0.32.1) 学习笔记(逐行注释)(八)按键事件
  • vscode 中自己使用的 launch.json 设置
  • SpringBoot中实现接口查询数据动态脱敏
  • 倍福下的EC-A10020-P2-24电机调试说明
  • NVIDIA Nsight Systems性能分析工具
  • ISO 22341 及ISO 22341-2:2025安全与韧性——防护安全——通过环境设计预防犯罪(CPTED)
  • 武大智能与集成导航小组!i2Nav-Robot:用于的室内外机器人导航与建图的大规模多传感器融合数据集
  • 【字母异位分组】
  • 火车头使用Post方法采集Ajax页面教程
  • 量子计算驱动的Python医疗诊断编程前沿展望(中)
  • kubernetes-dashboard使用http不登录
  • 快速了解命令行界面(CLI)的行编辑模式
  • PyTorch框架之图像识别模型与训练策略
  • 一键部署开源 Coze Studio
  • 蓝牙链路层状态机精解:从待机到连接的状态跃迁与功耗控制
  • 全面解析了Java微服务架构的设计模式
  • 新疆地州市1米分辨率土地覆盖图
  • GOLANG 接口
  • 可自定义的BMS管理系统
  • 论文阅读:Inner Monologue: Embodied Reasoning through Planning with Language Models