volatile关键字

 

Java的volatile关键字用于把变量标记在主存中(main menory)。准确来说,保证该变量读写都是基于主存操作的,而不是CPU缓存。

 

变量的可见性问题

volatile可以保证不同线程之间对于该变量的可见性(让所有线程都看见它的变化)。

 

举个栗子:

 

如果在多线程环境中中操作非volatile变量,那么每个线程会把变量从主存复制到CPU缓存里,再进行变量操作(这么做有助于提高效率)。如果你的计算机是多核的。每个线程很有可能运行在不同的CPU上。这意味着,每个线程都会把主存中的变量复制到不同的CPU中。

 

 

       非volatile变量并不保证JVM虚拟机都会从主存中刷到CPU缓存中,或者从CPU缓存写到主存中。这样一来,就可能导致一些严重的问题。

 

想象下多线程访问下面的变量:

public class SharedObject {



    public int counter = 0;



}

 

      假如只有Thread1增加了变量,但是Thread1和Thread2都会不时读取这个变量。

 

      记住非volatile变量并不保证counter变量会从CPU缓存中写入到主存中。这很可能导致CPU缓存中的值和主存中的值不一致。导致其它线程仍然操作原有变量值。

 

       CPU缓存和主存中的值不一致,会导致其它线程看不见变量的变化,这就是所谓的”可见性”问题。一个线程更新了变量,但是其它线程并不知道,谓之不可见。

 

volatile保证可见性

 

         那么为了保证变量的可见性,volatile关键字诞生了。

 

         volatile修饰的变量会保证得操作的结果立即同步到主存中;读操作也会从主存中读取变量值了。这就保证了写操作对其它线程的可见性。

public class SharedObject {



    public volatile int counter = 0;



}

volatile可以保证, 但是如果多个线程同时修改变量值,v olatile就不太够用了。

 

Full volatile Visibility Guarantee


 

public class MyClass {

    private int years;

    private int months

    private volatile int days;





    public void update(int years, int months, int days){

        this.years  = years;

        this.months = months;

        this.days   = days;

    }

}

        因为volatile变量参与了total相加操作,那么结果就是years和months都会同days一样加载到主存中, 读取三个变量的时候都会读取到最新的变量值。

 

重新排序的问题

 

        其实在JVM和CPU工作时, 只有指令的语义相同, 那JVM和CPU就有权利打着提高效率的旗号来对程序中的指令进行排序。看下面有一个栗子:

      int a = 1;

      int b = 2;

 

      a++;

      b++;

会被排序成这样:

      int a = 1;

      a++;

 

      int b = 2;

      b++;

 

悲催的是,当有变量是volatile变量时,麻烦就来了。再来看MyClass.class栗子:

public class MyClass {

    private int years;

    private int months

    private volatile int days;





    public void update(int years, int months, int days){

        this.years  = years;

        this.months = months;

        this.days   = days;

    }

}

被机器翻译后的顺序:

public void update(int years, int months, int days){

    this.days   = days;

    this.months = months;

    this.years  = years;

}

 

       当days被修改时,months和years同时会被写到主存中,但是注意这时候months和years还没有赋值呢,再赋值时对其它线程就不可见了。

 

出现问题,解决问题,无限循环…。

The Java volatile Happens-Before Guarantee

Java volatile的Happens-Before保证机制

 

To address the instruction reordering challenge, the Java volatile keyword gives a "happens-before" guarantee, in addition to the visibility guarantee. The happens-before guarantee guarantees that:

为了解决指令重排问题,volatile关键字提出了一套”happens-before”机制,除了保证可见性外,这套机制还能保证:

 

Reads from and writes to other variables cannot be reordered to occur after a write to a volatile variable, if the reads / writes originally occurred before the write to the volatile variable.

The reads / writes before a write to a volatile variable are guaranteed to "happen before" the write to the volatile variable. Notice that it is still possible for e.g. reads / writes of other variables located after a write to a volatile to be reordered to occur before that write to the volatile. Just not the other way around. From after to before is allowed, but from before to after is not allowed.

Reads from and writes to other variables cannot be reordered to occur before a read of a volatile variable, if the reads / writes originally occurred after the read of the volatile variable. Notice that it is possible for reads of other variables that occur before the read of a volatile variable can be reordered to occur after the read of the volatile. Just not the other way around. From before to after is allowed, but from after to before is not allowed.

The above happens-before guarantee assures that the visibility guarantee of the volatile keyword are being enforced.

happens-before机制保证了volatile变量的可见性。

 

volatile其实还不够好

 

         即使volatile变量保证了该变量读取都是基于主存的,仍然可能出现一些bug…

        实际上,当多线程对volatile进行写操作, 如果新值不依靠旧值,那存储在主存中的结果不能说错,对吧。但是一旦新值需要对旧值进行操作,结果的可见性可就不保证了。

        在读写的间隙,线程1把主存中的0读到了CPU缓存中,变为1,并没有写会到主存中,线程2又重复了这一步骤。

         这时候线程1和线程2就出现了不同步,counter的值本应该是2,但是每个线程在不同CPU缓存的值是1,主存的值还是0着呢,即使线程把值写会到了主存,结果也是错的。

 

volatile什么时候适用?

        之前说到了,如果两个线程都读和写一个共享变量,这时候仅使用volatile就不够了。需要使用synchronized来保证变量操作的原子性。读或写一个volatile变量并不会导致线程阻塞,相应保证原子性就必须使用synchronized关键字包主关键的部分。

 

       总结,volatile保证一写多读的变量可见性,但是不保证多写多读的变量可见性。操作主存比访问CPU缓存要消耗性能多了,访问volatile变量也会组织指令重排序,这也是一种常见的提升性能的技巧。因此当你需要提高变量可见性时,那一定要使用volatile关键字修饰变量。

内容概要:本文介绍了奕斯伟科技集团基于RISC-V架构开发的EAM2011芯片及其应用研究。EAM2011是一款高性能实时控制芯片,支持160MHz主频和AI算法,符合汽车电子AEC-Q100 Grade 2和ASIL-B安全标准。文章详细描述了芯片的关键特性、配套软件开发套件(SDK)和集成开发环境(IDE),以及基于该芯片的ESWINEBP3901开发板的硬件资源和接口配置。文中提供了详细的代码示例,涵盖时钟配置、GPIO控制、ADC采样、CAN通信、PWM输出及RTOS任务创建等功能实现。此外,还介绍了硬件申领流程、技术资料获取渠道及开发建议,帮助开发者高效启动基于EAM2011芯片的开发工作。 适合人群:具备嵌入式系统开发经验的研发人员,特别是对RISC-V架构感兴趣的工程师和技术爱好者。 使用场景及目标:①了解EAM2011芯片的特性和应用场景,如智能汽车、智能家居和工业控制;②掌握基于EAM2011芯片的开发板和芯片的硬件资源和接口配置;③学习如何实现基本的外设驱动,如GPIO、ADC、CAN、PWM等;④通过RTOS任务创建示例,理解多任务处理和实时系统的实现。 其他说明:开发者可以根据实际需求扩展这些基础功能。建议优先掌握《EAM2011参考手册》中的关键外设寄存器配置方法,这对底层驱动开发至关重要。同时,注意硬件申领的时效性和替代方案,确保开发工作的顺利进行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值