zoukankan      html  css  js  c++  java
  • java内存模型与线程-volatile变量的特殊规则

    一、规则总结

    轻量级的同步机制,变量V为volatile类型。

    (1) 在工作内存中,每次使用V前都要先从主内存刷新最新的值,用于保证能看见其它线程对变量V所做的修改后的值。

    (2) 在工作内存中,每次修改V后都立刻同步到主内存中,用于保证其它线程看到自己对变量V所做的修改。

    (3) 对volatile变量的修改不会被指令重日东月西 ,保证代码的执行顺序与程序的顺序相同。

    volatile变量的读性能与普通变量区别不大,但是写操作性能 差一些,然而大多数情况下比加锁要好。

    二、两个特点 

    当一个变量定义成volatile后,它具备两个特点:

    • 保证此变量对所有线程的可见性

    也就是当一个线程修改了这个变量的值,新值对于其它线程来说是可以立马得得知的。而普通变量在值在线程间传递要通过主内存来完成,如,一个线程A修改了一个普通变量,然后向主内存写回,另外一个线程B在线程A回写了后,再从主内存中读取到新的值,新的变量才对线程B可见。

    我们可以说volatile变量在各个线程的工作内存中不存在一致性的问题(在各个线程的工作内存中,volatile变量也可以存在不一致的情况,但是由于每次使用前面都进行了刷新,所以看不到一不致的情况,所以我们可以认为不存在一致性问题),但是Java里面的运算并不具备原子性,导致volatile变量的运算在并发情况下也不安全。

    例如 

    package com.company;
    
    /**
     * Created by lsj on 2015/9/7.
     */
    public class VolatileTest {
        public static volatile int race =0;
    
        public static void increase(){
            race++ ;
        }
    
        private static final int COUNT =20;
    
        public static void main(String [] args){
            System.out.println("begin");
            Thread [] threads = new Thread[COUNT] ;
            for (int i=0;i<COUNT;i++){
                threads[i]= new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int i=0;i<100;i++){
                            increase();
                        }
                    }
                }) ;
                threads[i].start();
            }
            //Thread中yield()是让当前线程暂停,转入就绪状态,
            //让系统的线程调度器重新调度一次,这里已经排队了main线程。
            while (Thread.activeCount()>1){
                Thread.yield();
            }
            System.out.println(race);
    
        }
    }  

    输出 的结果 不一定正确 。

    问题出在race++中,我们可以得到这一句的字节码:

    public static void increase();
      Code :
      Stack =2,Locals=0, Args_size =0
       0:  getstatic    #13;  //Field race:I
       3:  iconst_1
         4:  iadd 
        5:  putstatic    #13 ;  //Field race:I
         8:  return 
    

    失败的原因:当getstatic将race取到操作栈的时候,volatile 保证了race的值在此时是正确的,但是在执行iconst_1, iadd时,其它的线程可能已经修改了race,而在操作数栈中的值就变成了过期的数据 ,所以putstatic指令就可能只是将不正确的race写入到了内存中。实际上这里的字节码也不一定是原子性的,但是已经可以说明问题了。

    由于volatile只能保证可见性,运算场景一定要符合下面的条件才能使用volatile:

    (1) 运算结果并不依赖于变量的当前值,或者能够确保只有单一的线程修改变量的值 。

    (2) 变量不需要与其它状态 变量共同参与 不变约束。

    其它情况的运算场景下我们要加锁处理。

    • 禁止指令重排序优化
  • 相关阅读:
    [搬运] Tina R329 swupdate OTA 步骤
    摄像头 ISP 调试的经验之谈(以全志 AW hawkview 为例)
    2021 年了 在 WSL2 中使用 adb 、fastboot 的等命令
    wsl2 编译 linux openwrt 项目的时候,经常会出现 bash: -c: line 0: syntax error near unexpected token `('
    sipeed v833 硬件验证以及开发记录(2021年5月18日)
    Allwinner & Arm 中国 & Sipeed 开源硬件 R329 SDK 上手编译与烧录!
    把 R329 改到 ext4 sdcard 启动变成 Read-Only 系统,导致没有文件修改权限后如何修复。
    linux kernel version magic 不一致导致的模块 加载 (insmod) 不上
    剑指 Offer 17. 打印从1到最大的n位数
    剑指 Offer 16. 数值的整数次方
  • 原文地址:https://www.cnblogs.com/chuiyuan/p/4789831.html
Copyright © 2011-2022 走看看