zoukankan      html  css  js  c++  java
  • java并发编程之volatile

      首先是一段简单的多线程代码

    public class VolatileTest {
    
        private boolean flag = true;
    
        public boolean isFlag() {
            return flag;
        }
    
        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    
        public static void main(String[] args) throws InterruptedException, IOException {
            VolatileTest volatileTest = new VolatileTest();
            Thread thread =new Thread(() -> {
                int i = 1;
                while (volatileTest.isFlag()){
                    i++;
                }
                System.out.println(i);
            });
            thread.start();
            Thread.sleep(2000);
            volatileTest.setFlag(false);
            System.out.println("设置结束标示");
    
        }
    
    }

      运行main方法后,我们发现线程并不能被有效的终止,这其中有两个原因:1、cpu高速缓存2、JIT优化

      在java中,我们通常把线程独享的区域称为工作内存(栈),而把线程共享的区域称为主内存(堆),现代cpu为了提升处理效率,会在cpu和内存之间添加三级cpu高速缓存,因为cpu高速缓存,导致线程中不能读取到共享变量的变化(短时间内),导致可见性问题,而JIT编译器则会对我们的代码进行优化,查看JIT优化后的汇编代码,可以使用JITwatch(当然前提是看得懂汇编),优化后的伪代码大概是这样

    boolean f = demo1.flag;
    if(f){
      while(true) {
        i++;
      }
    }

      这两个原因导致demo中的线程不能正确终止,除了给flag变量添加volatile关键字,我们还可以使用以下手段

      1、如果是32的jdk,则可以在运行参数上加上-client,客户端模式中jdk不会对我们的代码进行JIT优化

      2、64位jdk没有client运行模式,client和server的主要区别在于JIT优化,server模式下可以通过-Djava.compier=NONE关闭JIT优化

      3、使用synchronized关键字,所有同步操作都必须保证原子性和可见性,所以在同步块中发生的变化会立马写回主存,我们需要对修改flag值和读取flag值的代码进行同步

      4、while循环中,调用sleep()方法或者ew byte[1024*1024*1],让出cpu,JVM针对现在的硬件水平已经做了很大程度的优化,基本上很大程度的保障了工作内存和主内存的及时同步,相当于默认使用了volatile。但只是最大程度!在CPU资源一直被占用的时候,工作内存与主内存中间的同步,也就是变量的可见性就会不那么及时。CPU一直被占用的时候,数据的可见性得不到很好的保证,就像上面的程序一直循环做i++;运算占用CPU,而如果调用sleep()或者new byte[1024*1024*1]操作来说,CPU已经不是主要占时间的操作,真正的耗时应该在内存的分配上(因为CPU的处理速度明显快过内存,不然也不会有CPU的寄存器了),所以CPU空闲后会遵循JVM优化基准,尽可能快的保证数据的可见性,从而从主存同步is变量到工作内存,最终导致程序结束。

      JVM指令重排序遵从if-as-serial ,即保证单个cpu中最终结果不变,多cpu多线程时,多个内存区间进行交互势必会有问题。为了解决这个问题,java语言提出内存模型的相关规范,定义了程序在每个点上可以读取到什么值,官网文档地址:https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4

      java内存模型的定义

      happen-before原则(先发生先生效)

      JVM规范对volatile的定义,官网文档地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html,通过javap命令反编译字节码文件,反编译后可以看到volatile变量有 ACC_VOLATILE访问控制符,在JVM规范中,可以看到对ACC_VOLATILE访问控制符修饰的变量是禁用缓存的。

      综合下来,volatile关键字,根据java内存模型规范,对volatile变量v的写操作,对其它线程后续对v的读操作保持可见,为了满足规范,JIT有时候就不会对volatile修饰的变量进行优化,也就是不进行指令重排序;根据jvm规范,volatile变量v读操作总是能及时读取到主内存的最新值。

      

  • 相关阅读:
    Laravel Passport token过期后判断refresh_token是否过期
    js 数组随机排序
    jquery的animate关于background-position属性
    css hack 汇整
    顶部导航--向上滚动的时候出现,向下滚动的时候隐藏
    手机端全局样式表整理(mobile)
    AR专用汉明码
    css常用命名规则
    晚11点
    当 IDENTITY_INSERT 设置为 OFF 时,不能为表‘XXX’中的标识列插入显式值。
  • 原文地址:https://www.cnblogs.com/hhhshct/p/11477652.html
Copyright © 2011-2022 走看看