java并发编程(十三)之线程Volatile内存可见性

Java领域JVM的并发编程中的内存可见性 什么是内存可见性为什么会出现内存可见性问题JVM如何解决内存可见性问题如何在实际编程中正确使用相关机制范围涵盖从Java内存模型(JMM)基础到高级并发编程技巧,重点分析volatile关键字的实现原理和使用场景。文章将从基础概念入手,逐步深入JVM实现细节,最后给出实际应用案例和性能优化建议。包含理论分析、代码示例和基准测试结果。内存可见性:一个线程对共享变量的修改能够及时被其他线程看到:Java内存模型定义的两个操作之间的偏序关系内存屏障:CPU指令,用于控制特定类型的内存操作顺序。 阅读详情

一、内存可见性

Java Memory Model (JAVA 内存模型)描述线程之间如何通过内存(memory)来进行交互。 具体说来, JVM中存在一个主存区(Main Memory或Java Heap Memory),对于所有线程进行共享,而每个线程又有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。

package com.dason.juc;

/*
 * 一、volatile 关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见。
 * 					  相较于 synchronized 是一种较为轻量级的同步策略。
 * 
 * 注意:
 * 1. volatile 不具备“互斥性”
 * 2. volatile 不能保证变量的“原子性”
 */
public class TestVolatile {
	
	public static void main(String[] args) {
		ThreadDemo td = new ThreadDemo();
		new Thread(td).start();
		
		while(true){
			if(td.isFlag()){
				System.out.println("------------------");
				break;
			}
		}
		
	}

}

class ThreadDemo implements Runnable {
	//保证了内存可见性
	private volatile boolean flag = false;

	@Override
	public void run() {
		
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
		}

		flag = true;
		
		System.out.println("flag=" + isFlag());

	}

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}

}

二、缺陷

Volatile虽然保证了线程中变量内存的可见性,但是他不能实现线程之间的互斥性以及变量的原子性。互斥性的实现通常我们采用的是同步的方式(其实同步Synchronized就已经保证了这三个特性,因为它拒绝同一时刻多个线程对同一资源的访问,Synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高。)。那么什么是原子性呢?即不可再分割,不可被中断。

在 java.util.concurrent.atomic 包下提供了一些原子变量。使用Volatile保证其内存可见性,采用CAS算法保证数据变量的原子性。其中以Atomic开头的包装类。例如AtomicBoolean,AtomicInteger,AtomicLong。它们分别用于Boolean,Integer,Long类型的原子性操作。

package com.dason.juc;

import java.util.concurrent.atomic.AtomicInteger;

/*
 * 一、i++ 的原子性问题:i++ 的操作实际上分为三个步骤“读-改-写”
 * 		  int i = 10;
 * 		  i = i++; //10
 * 
 * 		  int temp = i;
 * 		  i = i + 1;
 * 		  i = temp;
 * 
 * 二、原子变量:在 java.util.concurrent.atomic 包下提供了一些原子变量。
 * 		1. volatile 保证内存可见性
 * 		2. CAS(Compare-And-Swap) 算法保证数据变量的原子性
 * 			CAS 算法是硬件对于并发操作的支持
 * 			CAS 包含了三个操作数:
 * 			①内存值  V
 * 			②预估值  A
 * 			③更新值  B
 * 			当且仅当 V == A 时, V = B; 否则,不会执行任何操作。
 */
public class TestAtomicDemo {

	public static void main(String[] args) {
		AtomicDemo ad = new AtomicDemo();
		
		for (int i = 0; i < 10; i++) {
			new Thread(ad).start();
		}
	}
	
}

class AtomicDemo implements Runnable{
	
	//不保证变量的原子性
//	private volatile int serialNumber = 0;
	
	private AtomicInteger serialNumber = new AtomicInteger(0);

	@Override
	public void run() {
		
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
		}
		
		System.out.println(getSerialNumber());
	}
	
	public int getSerialNumber(){
//		return serialNumber++;
		return serialNumber.getAndIncrement();
	}
	
	
}

那么问题又来了,什么又是CAS算法呢?

三、CAS算法

CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。

CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值B,要修改的新值B。

当且仅当 V == A 时, V = B; 否则,不会执行任何操作。

举例:

1.在内存地址V当中,存储着值为10的变量。
2.此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。
线程1:A = 10   B = 11
3.在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。
线程2:V = 11
4.线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。
线程1:A = 10   B = 11(此时 V = 11)
A != V 失败
5.线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋。
A = 11 B = 12
6.这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的。
A == V 
7.线程1进行SWAP,把地址V的值替换为B,也就是12。

V = B

package com.dason.juc;

/*
 * 模拟 CAS 算法(没有实现自旋)
 */
public class TestCompareAndSwap {
	
	public static void main(String[] args) {
		final CompareAndSwap cas = new CompareAndSwap();
		
		/**
		 * 可能成功,可能失败。
		 * 失败的时候,第一个线程在比较的时候,另一个线程就将其修改了
		 */
		for (int i = 0; i < 10; i++) {
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					int expectedValue = cas.get();
					boolean b = cas.compareAndSet(expectedValue, (int)(Math.random() * 101));
					System.out.println(b);
				}
			}).start();
		}
		
	}
	
}

class CompareAndSwap{
	private int value;
	
	//获取内存值
	public synchronized int get(){
		return value;
	}
	
	//比较 无论成功与否都返回旧值
	public synchronized int compareAndSwap(int expectedValue, int newValue){
		int oldValue = value;//1.拿到内存地址V
		
		if(oldValue == expectedValue){//2.比较预期值与内存值
			this.value = newValue;//3.将内存值替换为新值
		}
		
		return oldValue;
	}
	
	//设置: 成功就替换并返回true,失败返回false
	public synchronized boolean compareAndSet(int expectedValue, int newValue){
		return expectedValue == compareAndSwap(expectedValue, newValue);
	}
}
CAS的缺点:
1.CPU开销较大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
2.不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
3.ABA问题
这是CAS机制最大的问题所在。
什么是ABA问题?怎么解决?请参见 ABA问题。
Imputation Methods for scRNA Sequencing Data 越来越多的研究者使用单细胞RNA测序(scRNA-seq)技术来表征单细胞水平的转录图谱。他们用它来研究复杂组织的异质性、转录组动力学和未知生物的多样性。然而,在基因表达模式随机性的scRNA-seq数据中普遍存在许多技术和生物学上的噪音。这些数据通常具有高维、稀疏和大量“dropout”值的特点,并且受批处理效应的影响。scRNA-seq数据中大量的“dropout”值严重掩盖了基因之间的重要关系,阻碍了下游分析。因此,scRNA-seq数据dropout值的估算就显得尤为重要。 阅读详情

相关推荐

数字孪生成熟度的L0-L4五级划分,各自特征和应用场景

数字孪生的分级(或成熟度模型)通常用于描述其技术复杂度和功能完备性的演进过程。3D设计模型(CAD)、建筑信息模型(BIM)、产品数字化手册。全自动驾驶系统、自适应智能电网、 “黑灯工厂”的自主调度。多学科模型融合、 “What-if”分析、提供决策建议。IoT设备监控平台、预测性维护(报警)、智慧仪表盘。工艺仿真优化、资产性能管理、手术方案预演、虚拟调试。状态监控、历史回溯、阈值报警、根因分析。可视化、数字化档案。自优化、自适应、目标驱动、高度自治。传统设备、纸质记录的管理模式。无或仅有离线、孤立数据。

2401_82881178的博客 1554

在jvm client模式中,不使用volatile也可以保证可见性原因

而Java内存模型规定所有的变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但是线程对这些变量的操作只能在自己的工作内存中进行,不能直接操作主内存中的变量,要先将变量从主内存中拷贝到线程自己的工作内存中再对其进行操作,操作完成后再将操作后的变量写回主内存当中,因此不同的线程也无法访问对方的工作内存,线程间的通信必须通过主内存来完成。在虚拟机的client模式下,由于JIT并没有做出足够的优化,在Thread2修改flag变量的状态后,Thread1可发现这个改动,并退出程序。

python_longway的博客 607

从零开始掌握Python机器学习:七步教程 基础篇

前言 「开始」往往是最难的,尤其是当选择太多的时候,一个人往往很难下定决定做出选择。本教程的目的是帮助几乎没有 Python 机器学习背景的新手成长为知识渊博的实践者,而且这个过程中仅需要使用免费的材料和资源即可。 第一步:基本 Python 技能 如果我们打算利用 Python 来执行机器学习,那么对 Python 有一些基本的了解就是至关重要的。 首先,你需要安装 Python。因为我们后...

hcgx2018的博客 1613

咦,咋没加volatile,变量也可见的呢?

本想今天复习一下volatile的,结果在写demo时,怎么也达不到效果。还以为写错的呢。最后发现是 System.out.printlnr惹的祸。 我的demo如下: 定义一个变量控制线程的中断。 最初的demo是这样的: class Test1{ //public volatile static int num =0; public static int num =0; public static void main(String[] args) { new Th

LoveMyLife 1604

电子发票二维码背后的秘密:10分钟看懂校验码、加密字符含义与防伪设计

本文深入解析电子发票二维码的结构与防伪设计,揭示校验码和加密字符的生成原理及验证方法。通过实际案例和技术细节,帮助财务人员理解电子发票的安全机制,提升防伪识别能力,确保税务合规。

weixin_29248611的博客 530

couldn‘t upgrade db schema: insert into ACT_GE_PROPERTY values (‘common.sche[已解决]

启动整合了flowable的项目,初始化时报错。flowable没有自动建表, 原因 测试数据库版本是MySQL5.7。经过查询和尝试,原因是:在这个数据库里面,已经有一个数据库里面有activiti的这些表了,它检索的时候全库检索查到它了,所以flowable不去建表了,但是我们指定的这个schema里卖弄还是空的,所以它报了找不到表。解决方法:mysql的链接上加上参数如下,让本schema成为默认的schema。...

JavaPub 6016

Java并发之volatile关键字内存可见性问题

Java并发之volatile关键字内存可见性问题 线程之间数据共享案例 我们先来看一个场景: Main函数启动后,调用一个线程向list中添加数据。List的size为5的时候,设置变量flag为true.然后,主线程根据flag的值进行其他操作。 代码如下: 编辑 运行结果: 编辑 我们发现,当子线程输出flag为ture后,主线程也没有输出=====。 这是为什么呢? 线程在内存中运行简图 我们来看看上面程序在内存中怎么运行的 编辑 运行说明: 当程

203

YOLOv11改进-上采样 | EUCB高效上卷积块,实现特征图尺度匹配和高效上采样

本文介绍了一种在YOLOv11目标检测模型中引入高效解码器模块EMCAD的创新方法,以提升模型在资源受限场景下的性能与效率。EMCAD由多个模块构成,其中核心的EUCB(高效上卷积块)通过上采样、深度可分离卷积、激活归一化和通道调整等操作,兼顾了特征质量与计算成本。实验结果显示,该模块在显著减少参数与FLOPs的同时仍具备优异性能。文章还提供了完整的YOLOv11模型集成流程、配置和训练实战。

魔改工程师的博客 1540

volatile解决Java内存可见性问题

在java的世界中存在的各种各样的关键字,就像其他的语言一样,这些关键字组成了这个丰富的java的世界。有很多关键字大家是比较的熟悉的,但是还有一些关键字,连作者自己都没有遇到过或者是遇到过也没有使用过。今天我们就来聊聊”volatile”. java开发中有内存这个概念,我想大家没有不知道的吧,代码在虚拟机中执行是需要将code读到内存中的。 一个人的世界 如果这块儿内存空间是单个“人”用...

JavaKotlin技术专栏 403

(功能安全六)硬件FMEDA计算

FMEDA(故障模式、影响与诊断分析)是功能安全验证的核心方法,通过量化分析硬件故障模式及其影响,计算SPFM、LFM、PMHF等关键指标。分析过程包含六大步骤:计算元器件失效率、识别故障模式、评估安全机制诊断覆盖率、分类计算各类失效率、量化指标计算及设计优化。输入需要系统架构图、硬件故障模式清单等资料,输出安全指标达标情况及改进建议,确保设计满足ASIL等级要求。该方法通过系统化的故障分类和量化评估,为硬件安全设计提供科学依据。

Dayongger的博客 1453

cesium 气象云(体积云)

本文介绍了基于Cesium的气象云渲染技术方案,其核心是通过GPU加速的光线投射算法实现气象云的三维可视化。方案采用分层架构设计:GPU计算层由顶点着色器和片段着色器组成,负责光线投射计算、3D纹理采样和颜色映射;CPU控制层处理数据预处理、纹理创建和参数配置。关键技术包括:顶点着色器计算相机位置和光线方向,片段着色器实现光线步进采样、颜色映射和半透明混合。该方案高效解决了气象数据与地理场景的融合问题,支持云图、温度场等复杂三维气象数据的实时渲染,具有高真实感和良好性能。

God's asylum的博客 1204

【小家java】使用volatile关键字来实现内存可见性、实现轻量级锁

内存可见性 volatile是Java提供的一种轻量级的同步机制,在并发编程中,它也扮演着比较重要的角色。同synchronized相比(synchronized通常称为重量级锁),volatile更轻量级,相比使用synchronized所带来的庞大开销,倘若能恰当的合理的使用volatile,自然是美事一桩。   为了能比较清晰彻底的理解volatile,我们一步一步来分析。首先来看看如下...

架构师:通透,才能写出好代码! 1836

【制作100个Unity游戏之35】从零开始构建类《月圆之夜》《杀戮尖塔》的卡牌游戏(附带项目源码)

本文介绍了如何从零开始构建类似《月圆之夜》《杀戮尖塔》的卡牌游戏。主要内容包括:1)获取游戏素材资源;2)导入xyframe框架工具集;3)实现手牌弯曲排列效果,通过样条曲线控制卡牌位置和旋转;4)设计可扩展的MVD卡牌系统架构,分离数据层(ScriptableObject存储)、模型层(业务逻辑)和视图层(显示控制)。文中提供了完整的代码实现,并展示了卡牌动态排列的GIF效果演示。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 1429

Java并发:volatile内存可见性和指令重排

转载请注明出处:jiq•钦'stechnical Blogvolatile两大作用1、保证内存可见性2、防止指令重排 此外需注意volatile并不保证操作的原子性。内存可见性1 概念         JVM内存模型:主内存和线程独立的工作内存Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存(比如CPU的寄存器),线程只能访问自己的工作内存,不可以访问

jiq•钦's technical Blog 1万+

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

【JavaEE初阶 — 多线程】内存可见性问题 & volatile

【JavaEE初阶 — 多线程】讲解内存可见性问题的产生,引入volatile 关键字,详解JMM模型

OuO-1 的博客 1255

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

【多线程初阶】内存可见性问题 & volatile

线程安全问题:内存可见性问题 & 编译器优化 & volatile & Java内存模型(JMM)

2503_90322569的博客 1368

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

并发编程-06线程安全性之可见性 (synchronized + volatile)

文章目录可见性定义导致不可见的原因可见性 -synchronized (既保证原子性又保证可见性)可见性 - volatile(但不保证操作的原子性)volatile变量 写操作volatile变量 读操作使用volatile尝试解决计数并发错误的问题 【volatile无法解决该问题】volatile使用场景synchronized和volatile的比较 可见性定义 一个线程对共享变量值的修改...

小工匠 2万+

Java并发编程实战 Day 3:volatile关键字与内存可见性

Java内存模型(Java Memory Model, JMM)定义了Java程序中变量的访问规则,屏蔽了不同硬件平台和操作系统的差异,保证了Java程序在各种平台下对内存的访问效果一致。在JMM中,每个线程都有自己的本地内存(Local Memory),其中保存了主内存(Main Memory)中该线程使用的变量副本。线程读写变量时,默认情况下只能访问本地内存,这可能导致多个线程看到的变量值不一致。今天我们学习了Java中volatile关键字的使用方法及其背后的工作机制。

在未来等你的专栏 912

并发编程 之 可见性、原子性、volatile、synchronized、Java内存模型的深入剖析

代码执行的顺序没有按照预想的顺序进行,造成了有序性问题。有序分为相对有序、绝对有序。造成指令乱序的根本原因是:CPU指令重排序java中的线程间采用共享内存的方式进行隐式通信,同步则需要显示操作。如果不做显示的同步操作,多个线程间的操作顺序会不可控,不了解这种机制,就会遇到各种奇怪的内存不可见问题概要Java内存模型(JMM)是Java虚拟机并发知识中很重要的一部分,为了更好的理解它。我们先花费一点时间去了解物理计算机中的并发问题。

RubyWinner的博客 777

【多线程】之volatile关键字与内存可见性问题

主要讲述了volatile关键字以及如何解决内存可见性问题

fayifenjihuang的博客 1159

JavaEE之线程(5)——Java内存模型、内存可见性、volatile关键字

以上就是今天要讲的内容,在并发三特征的可见性中,volatile通过新值立即同步到主内存和每次使用前从主内存刷新机制保证了可见性。通过禁止指令重排序保证了有序性。无法保证原子性;synchronized关键字通过lock和unlock操作保证了原子性,通过对一个变量unlock前,把变量同步回主内存中保证了可见性,通过一个变量在同一时刻只允许一条线程对其进行lock操作保证了有序性。

2301_80653026的博客 1019

1-volatile关键字 内存可见性

假设内存中有个变量a=1;需要进行运算得到a的新值。 在这个过程中:是通过CPU运算进行操作的。计算的过程 是从内存中将变量a 复制一份然后计算后,将返回的值重新写入内存中的变量a。(CPU是进行运算的、而内存是用来存储的。这种特性导致运算过程必须是拷贝一份数据给cpu运算,然后将结果重新返回给内存) 上面的过程相当于一个线程操作,当有多个线程进行运算时那么就会出现一些问题: 由于每个线程...

诗水人间 13万+

java多线程之内存可见性(浅谈synchronize 和volatile)

注:本博客是慕课网的一个免费课程的学习笔记, 有兴趣的同学可以去看看: 细说Java多线程之内存可见性 什么是内存可见性?      可见性: 一个线程对共享变量值的修改,能够及时地被其他线程看到 什么是共享变量?      共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量 什么是JMM?      Java内存模型JMM(Java Memo...

weixin_42354330的博客 2344

volatile保证可见性的验证

文章目录一、前言二、论证过程三、总结 一、前言     java内存模型中的可见性是指,当一个线程修改了共享变量的值后,其他线程可以立马知道这个修改后的值。在《Java并发编程:volatile关键字解析》中有这么一段话:   对于可见性,Java提供了volatile关键字来保证可见性。   当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它...

不骄不躁,磨砥刻厉 4082

关于volatile解决内存可见性问题(保证线程安全)

volatile是不保证原子性的,原子性是靠synchronized来保证的。但是volatile和synchronized都能保证线程安全。在多线程中,针对同一个变量,一个线程进行读取,一个线程进行修改,那么加上volatile关键字可以保证线程安全问题。

crazy_xieyi的博客 1270

volatile如何保证内存的可见性

目录 (一)内存可见性 (二)防止指令重排 (三)总结 (四)volatile和synchronized的区别 (一)内存可见性 JVM内存模型:主内存和线程独立的工作内存 Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存(比如CPU的寄存器),线程只能访问自己的工作内存,不可以访问其他线程的工作内存。 工作内存中保存了主内存共享变量...

huanglifu520的博客 2068
上一篇: java并发编程(十一)之线程交换器Exchanger<V>
下一篇: java并发编程(十四)之集合的并发修改异常
Dason_yu
博客等级 码龄10年 18粉丝 99原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值