美文网首页
java关键字-volatile

java关键字-volatile

作者: 一个喜欢烧砖的人 | 来源:发表于2018-08-24 12:04 被阅读10次
    前言
    • java 5之前这个关键字备受争议,java5只有volatile才得以重生
    • 因为volatile和java的内存模型有关(想弄清楚volatile就必须弄清相关的内存模型)
    java相关的内存模型的概念
    • 程序运行时临时数据存放在主存(物理内存中)
    • cpu 在告诉运作时cpu存在高速缓存
    • 就存在高速缓存和内存之间的数据交互
    • 问题:数据的高并发同步问题
    并发编程的三个概念
    • 原子性
      一个操作或者多个操作,要么全部执行,要么都不执行称为一个程序的原子性

    • 可见性
      多个线程同时访问一个变量,当一个线程改变了这个变量的值,其他线程能够立即 看到修改的值

    • 有序性
      程序执行的顺序按照代码的编写顺序执行(因为在代码执行阶段,处理器会对代码进行重排,但是这并不影响代码的最终执行结果)

    java 内存模型

    从代码层面分析前面提的三个概念

    • 分析代码的原子性操作
    x = 10;         //语句1
    y = x;         //语句2
    x++;           //语句3
    x = x + 1;     //语句4
    

    语句1是直接将数值10赋值给x,也就是说线程执行这个语句的会直接将数值10写入到工作内存中。
    语句2实际上包含2个操作,它先要去读取x的值,再将x的值写入工作内存,虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作,但是合起来就不是原子性操作了。
    同样的,x++和 x = x+1包括3个操作:读取x的值,进行加1操作,写入新的值。
    所以上面4个语句只有语句1的操作具备原子性。
    总结:
    java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和lock来实现,(因为锁可以保证同一时刻只有一个线程执行该代码块,从而保证了原子性)

    • 可见性
      1、对于可见性,volatile关键字来保证可见性
      当一个共享变量被volatile修饰时,他会保证修改的值会立即被更新到主存,当有其他线程读取时,他会去主存内从新读取
      2、而普通的共享变量不能保证可见性,因为不能保证什么时候写入朱存
      3、利用锁可以实现变量的可见性,因为在synchronized和lock能保证同一时刻只有一个线程获取锁执行同步代码,并且在释放锁之前会将变量的修改刷新到朱存当中来保证可见性;

    • 有序性
      在java内存模型中,允许编译器和处理器对指令进行重排,但是重排过程不会影响单线程的执行,却会影响多线程的并发执行顺序
      1、 java中 可以通过volatile关键字来保证有序性
      2、当然也可通过synchronized和lock来保证有序性
      3、java 先天存在happens-before原则,

    剖析volatile的关键字

    一旦一个变量被volatile修饰,就具备以下两种含义
    1、 保证了不同线程对于这个变量进行操作时的可见性
    2、禁止指令重排
    注意:维度不能保障原子性,
    首先先看个代码

    //线程1
    boolean stop = false;
    while(!stop){
        doSomething();
    }
     
    //线程2
    stop = true;
    

    很多人都会这么干,但是这个会不会出错呢?答案是会出错的,因为线程中的数据和全局的数据是一样的吗?
    解决的话 就是在stop 前面添加volatile关键字,他会在线程刷新数据的时候立马改变共享内存的数据
    所以说:volatile无法保证原子性

    • volatile保障原子性?
      无法保障
      如果想保障程序的原子性 可依靠sychronized或者lock 来实现

    • volatile保障可见性

    public class Test {
        public volatile int inc = 0;
         
        public void increase() {
            inc++;
        }
         
        public static void main(String[] args) {
            final Test test = new Test();
            for(int i=0;i<10;i++){
                new Thread(){
                    public void run() {
                        for(int j=0;j<1000;j++)
                            test.increase();
                    };
                }.start();
            }
             
            while(Thread.activeCount()>1)  //保证前面的线程都执行完
                Thread.yield();
            System.out.println(test.inc);
        }
    }
    

    这串代码就能保证最后的结果是10000吗?答案也是no
    因为虽然说线程在执行的时候会立马更新主存的数据,但是数据的++本身分为两个步骤,所以无法达到理想效果
    1、可采用java atomic 包下面的 atomicinteger 的增加来计算
    2、利用synchronized 和 lock 来实现原子性
    总结:volatile实现可见性,是一个线程(cpu)的数据改变,他会更新主存的数据改变,同时他会让其他线程(cpu)的数据失效。

    • volatile保证有序性
      线程中的顺序 会以volatile为分界,按顺序执行
    //x、y为非volatile变量
    //flag为volatile变量
     
    x = 2;        //语句1
    y = 0;        //语句2
    volatile flag = true;  //语句3
    x = 4;         //语句4
    y = -1;       //语句5
    

    分析:1、2是一组 谁在前谁在后不一定
    但是一定会在3前,同理,3肯定在4、5的前面,
    4、5谁在前谁在后不一定

    volatile的应用场景
    • synchronizd关键字是防止多个线程同时执行一段代码,那么他会很影响程序执行的效率,而volatile关键字在某些方面优于synchronized,但是注意volatile关键字无法替代synchronized关键字的,因为volatile无法保证程序的原子性,
    • 通常使用volatile有以下2个条件:
      1、对变量的改变不依赖于当前值
      2、对变量的依附不依赖于其他对象
      示例:
    volatile boolean flag = false;
     
    while(!flag){
        doSomething();
    }
     
    public void setFlag() {
        flag = true;
    }
    volatile boolean inited = false;
    //线程1:
    context = loadContext();  
    inited = true;            
     
    //线程2:
    while(!inited ){
    sleep()
    }
    doSomethingwithconfig(context);
    
    

    一般都是作为多线程标记用的

    相关文章

      网友评论

          本文标题:java关键字-volatile

          本文链接:https://www.haomeiwen.com/subject/xwtbiftx.html