JAVA多线程之volatile 与 synchronized 的比较

Vivado新手必看:5分钟搞定FPGA程序固化到Flash(附MCS文件生成全流程) 本文为FPGA新手详细解析了如何将程序固化到Flash存储器的完整流程。核心在于理解SRAM工艺FPGA断电易失的特性,并利用Vivado工具将比特流文件转换为Flash可识别的MCS文件。文章重点拆解了生成MCS文件的关键配置步骤、硬件连接要点,以及通过JTAG烧写Flash的实战操作,并提供了常见启动失败问题的排查清单,帮助开发者实现设计从调试到产品部署的关键跨越。 阅读详情

一,volatile关键字的可见性

要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下:

JAVA多线程之volatile 与 synchronized 的比较

从图中可以看出:

①每个线程都有一个自己的本地内存空间--线程栈空间???线程执行时,先把变量从主内存读取到线程自己的本地内存空间,然后再对该变量进行操作

②对该变量操作完后,在某个时间再把变量刷新回主内存

因此,就存在内存可见性问题,看一个示例程序:(摘自书上)

public class RunThread extends Thread {

    private boolean isRunning = true;

    public boolean isRunning() {
        return isRunning;
    }

    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }

    @Override
    public void run() {
        System.out.println("进入到run方法中了");
        while (isRunning == true) {
        }
        System.out.println("线程执行完成了");
    }
}

public class Run {
    public static void main(String[] args) {
        try {
            RunThread thread = new RunThread();
            thread.start();
            Thread.sleep(1000);
            thread.setRunning(false);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Run.java 第28行,main线程 将启动的线程RunThread中的共享变量设置为false,从而想让RunThread.java 第14行中的while循环结束。

如果,我们使用JVM -server参数执行该程序时,RunThread线程并不会终止!从而出现了死循环!!

原因分析:

现在有两个线程,一个是main线程,另一个是RunThread。它们都试图修改 第三行的 isRunning变量。按照JVM内存模型,main线程将isRunning读取到本地线程内存空间,修改后,再刷新回主内存。

而在JVM 设置成 -server模式运行程序时,线程会一直在私有堆栈中读取isRunning变量。因此,RunThread线程无法读到main线程改变的isRunning变量

从而出现了死循环,导致RunThread无法终止。这种情形,在《Effective JAVA》中,将之称为“活性失败”

解决方法,在第三行代码处用 volatile 关键字修饰即可。这里,它强制线程从主内存中取 volatile修饰的变量。

volatile private boolean isRunning = true;

扩展一下,当多个线程之间需要根据某个条件确定 哪个线程可以执行时,要确保这个条件在 线程 之间是可见的。因此,可以用volatile修饰。

综上,volatile关键字的作用是:使变量在多个线程间可见(可见性)

二,volatile关键字的非原子性

所谓原子性,就是某系列的操作步骤要么全部执行,要么都不执行。

比如,变量的自增操作 i++,分三个步骤:

  • 从内存中读取出变量 i 的值

  • 将 i 的值加1

  • 将 加1 后的值写回内存

这说明 i++ 并不是一个原子操作。因为,它分成了三步,有可能当某个线程执行到了第②时被中断了,那么就意味着只执行了其中的两个步骤,没有全部执行。

关于volatile的非原子性,看个示例:

public class MyThread extends Thread {
    public volatile static int count;

    private static void addCount() {
        for (int i = 0; i < 100; i++) {
            count++;
        }
        System.out.println("count=" + count);
    }

    @Override
    public void run() {
        addCount();
    }
}

public class Run {
    public static void main(String[] args) {
        MyThread[] mythreadArray = new MyThread[100];
        for (int i = 0; i < 100; i++) {
            mythreadArray[i] = new MyThread();
        }

        for (int i = 0; i < 100; i++) {
            mythreadArray[i].start();
        }
    }
}

MyThread类第2行,count变量使用volatile修饰

Run.java 第20行 for循环中创建了100个线程,第25行将这100个线程启动去执行 addCount(),每个线程执行100次加1

期望的正确的结果应该是 100*100=10000,但是,实际上count并没有达到10000

原因是:volatile修饰的变量并不保证对它的操作(自增)具有原子性。(对于自增操作,可以使用JAVA的原子类AutoicInteger类保证原子自增)

比如,假设 i 自增到 5,线程A从主内存中读取i,值为5,将它存储到自己的线程空间中,执行加1操作,值为6。此时,CPU切换到线程B执行,从主从内存中读取变量i的值。由于线程A还没有来得及将加1后的结果写回到主内存,线程B就已经从主内存中读取了i,因此,线程B读到的变量 i 值还是5

相当于线程B读取的是已经过时的数据了,从而导致线程不安全性。这种情形在《Effective JAVA》中称之为“安全性失败”

综上,仅靠volatile不能保证线程的安全性。(原子性)

此外,volatile关键字修饰的变量不会被指令重排序优化。这里以《深入理解JAVA虚拟机》中一个例子来说明下自己的理解:

线程A执行的操作如下:

Map configOptions ;
char[] configText;

volatile boolean initialized = false;

//线程A首先从文件中读取配置信息,调用process...处理配置信息,处理完成了将initialized 设置为true
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfig(configText, configOptions);//负责将配置信息configOptions 成功初始化
initialized = true;

线程B等待线程A把配置信息初始化成功后,使用配置信息去干活…..线程B执行的操作如下:

while(!initialized)
{
    sleep();
}

//使用配置信息干活
doSomethingWithConfig();

如果initialized变量不用 volatile 修饰,在线程A执行的代码中就有可能指令重排序。在 后端技术精选 公众号回复“后端面试”,获取最新面试资料。

即:线程A执行的代码中的最后一行:initialized = true 重排序到了 processConfig方法调用的前面执行了,这就意味着:配置信息还未成功初始化,但是initialized变量已经被设置成true了。那么就导致 线程B的while循环“提前”跳出,拿着一个还未成功初始化的配置信息去干活(doSomethingWithConfig方法)。。。。

因此,initialized 变量就必须得用 volatile修饰。这样,就不会发生指令重排序,也即:只有当配置信息被线程A成功初始化之后,initialized 变量才会初始化为true。综上,volatile 修饰的变量会禁止指令重排序(有序性)

三,volatile 与 synchronized 的比较

volatile主要用在多个线程感知实例变量被更改了场合,从而使得各个线程获得最新的值。它强制线程每次从主内存中讲到变量,而不是从线程的私有内存中读取变量,从而保证了数据的可见性。

比较:

①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法

②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。

synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。

四,线程安全性

线程安全性包括两个方面,①可见性。②原子性。

从上面自增的例子中可以看出:仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性。

把浮点数这件小事讲清楚:ABAP 7.51 里 Open SQL 与 ABAP CDS 的 FLTP 新玩法与避坑指南 ABAP浮点数计算的风险与应对 摘要:ABAP项目中的浮点数(FLTP)在跨平台计算时存在误差风险,同一逻辑在不同数据库平台可能产生细微差异。ABAP 7.51新增了两项关键能力:1)Open SQL和ABAP CDS中可为AVG函数指定结果类型,避免默认返回FLTP;2)ABAP CDS提供FLTP_TO_DEC转换函数。但需注意这些转换仍可能存在平台相关差异,且FLTP_TO_DEC会截断而非四舍五入小数位。开发者在涉及金额、数量等关键业务计算时,应明确数值语义并设计容错机制。 阅读详情

相关推荐

单细胞分析实战:用R语言绘制带连线的堆叠柱状图(附完整代码)

本文详细介绍了如何使用R语言绘制带连线的堆叠柱状图进行单细胞转录组数据分析,特别适用于展示细胞比例变化趋势。通过完整代码示例和实战技巧,帮助研究者优化多组别比较的可视化效果,提升科研图表质量。

yhn4567890的博客 390

java多线程中synchronized(同步块)和volatile的区别

1、通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用 synchronized 修饰的方法 或者 代码块。 2、用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最后值。 java中多线程在内存中的存取情况:每个线程都有一个线程栈,当用到这个线程时,首先访问到栈然

水木轩昊昊 857

Python自动化测试全套教程-第一天(python、pycharm开发环境的安装与配置(图文超级详细);附安装包)

本文介绍了Python开发环境的完整安装配置流程。主要内容包括:1) Python解释器的下载安装与版本检测方法;2) PyCharm IDE的下载安装步骤,包括专业版和社区版的选择;3) PyCharm的基础配置和项目创建方法;4) 开发环境的基本构造说明。文章提供了两种获取安装包的方式(官网下载和文章附件),并详细说明了解释器卸载、安装过程中的注意事项,以及如何验证环境配置成功。特别强调了PyCharm安装时的路径选择和虚拟环境的创建方法,为Python初学者提供了完整的开发环境搭建指南。

kiwi_king的博客 2036

Java 多线程之 synchronized 和 volatile 的比较

概述 在做多线程并发处理时,经常需要对资源进行可见性访问和互斥同步操作。有时候,我们可能从前辈那里得知我们需要对资源进行 volatile 或是 synchronized 关键字修饰处理。可是,我们却不知道这两者之间的区别,我们无法分辨在什么时候应该使用哪一个关键字。本文就针对这个问题,展开讨论。 版权说明 著作权归作者所有。 商业转载请联系作者获得授权,非商业...

weixin_30797027的博客 326

面板数据分析中的随机前沿软件FRONTIER Version 4.1应用

随机前沿模型是一种用于评估单位效率的统计方法。它考虑了输入和输出的随机性,允许我们在效率评估中引入不确定性。随机前沿模型通常用于评估生产单位、金融机构、医院等各种类型的决策单元的效率。FRONTIER是一个广泛使用的随机前沿分析软件,具有用户友好的界面和强大的功能。它提供了估计效率、前沿分析和模型比较的工具,使用户能够深入研究单位的效率和绩效。面板数据是一种包含多个单位(通常是时间序列数据)的数据类型,可以用于分析单位在不同时间点的变化和效率。

目标检测专栏持续更新中,改进YOLO系列通用,适用v5、v7、v8、所有博客均是团队原创博客,所有文章禁止转载,违者必究。 2983

nvprof:CUDA编程性能分析工具

NVIDIA提供了具有高性能计算的GPU产品,如何检验硬件的实际性能,写出高效的CUDA代码让算力加速卡物尽其用,那可以通过其配套提供的“护城河”软件工具链来检验和帮助发挥GPU的性能:nvprof。如下,对于将8192个A,B向量进行向量加法运算,即C[i] = A[i] + B[i],当软件设置不同的blocknum, threadnum时候,CUDA执行kernel的时间是不同的。比如如下命令,可以看到程序tmp1的总体性能,下面一行是打印gpu的执行时间情况。

Briwisdom的博客 3385

13.Java多线程之Synchronized和Volatile的比较

Java多线程之内存可见性和原子性:Synchronized和Volatile的比较      在说明Java多线程内存可见性之前,先来简单了解一下Java内存模型。     (1)Java所有变量都存储在主内存中     (2)每个线程都有自己独立的工作内存,里面保存该线程的使用到的变量副本(该副本就是主内存中该变量的一份拷贝)   (1)线程对共享变量的所有操作都必须在自己的工作内存中进行,不...

郑学炜的技术博客 354

Python爬虫实战:使用最新技术爬取GitHub Trending项目

本文将详细介绍如何使用Python最新技术栈构建一个高效的GitHub Trending爬虫。我们将使用异步请求库httpx、HTML解析库parsel、数据存储方案以及反反爬技术,完整实现一个能够稳定获取GitHub Trending数据的爬虫系统。文章包含5000余字详细教程,从环境搭建到代码实现,再到数据处理和存储,最后部署运行,提供全方位指导。GitHub Trending页面展示了GitHub上当前最受欢迎的项目,对于开发者了解技术趋势、发现优秀项目非常有价值。

2201_76125261的博客 739
上一篇: 涨姿势,Java中New一个对象是个怎么样的过程?
下一篇: 请不要再说Java中final方法比非final性能更好了
Java奥利给
博客等级 码龄6年 150粉丝 154原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值