欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java volatile关键字

程序员文章站 2022-05-31 20:54:19
...

前言:

        volatile用在并发编程中,主要用来解决缓存一致性。

1.内存模型

           程序在执行时,临时数据都是放入内存中的,cpu执行很快,要比在内存中取数据,写数据快很多,所以cpu里面有了高速缓存,实际程序在执行时,先将数据从内存复制到cpu告诉缓存中,cpu在执行过程中,就在告诉缓存cache中读取和写入数据,然后再刷新到主存中。在高并发情况下,就会出现问题,每个线程在运行时有自己的告诉缓存,cache中数据修改了,没来得及同步到内存,其他线程在内存中读取了数据,就造成了脏读,出现了缓存与主存不一致情况。下面图的两种方案时硬件上实现的:

java volatile关键字

 

2.并发编程三个概念

       1.原子性

             一个操作或者多个操作,要么全部执行并且执行过程中不被任何因素打断,要么全部失败都不执行。

       2.可见性

             多个线程同时访问同一个数据,一个线程改变了这个数据,其它线程能够立即看到修改后的值

       3.有序性

              程序执行的顺序按照代码的先后顺序执行,cpu不会进行指令重排序。

             指令重排序:处理器为了提高程序运行效率,它不保证语句的执行顺序和代码中一样,但是会保证执行的结果是一致的。    例如下面四条语句执行顺序是不确定的,顺序可能不一样,但是cpu会保证结果一样,在进行重排序时会考虑数据之间依赖性。

int i = 0;
int j = 1;
double tem = 10.1;
char a = 'a';

         因此,想要并发程序正确的执行,必须要保证原子性、可见性、有序性。只要有一个没有保证,程序就会运行不正确。

 

java内存模型:

           Java内存模型规定,所有的变量都是存在主存中,每个线程有自己的工作内存,线程对变量的所有操作都必须在工作内存中完成,而不能直接对主存进行操作,并且每个线程都不能访问其他线程的工作内存。

  1.原子性  在java中,对基本的变量的读取和赋值操作时原子性的,这些操作不可被中断,要么执行,要么不执行。

          x=1; 原子性

          a=b;不是原子性,需要先读取b,再赋值a

         如果要进行多个操作原子性的,可以通过synchronized和lock实现。

   2.可见性   java提供了volatile关键字来保证可见性。当一个变量被volatile修饰时,它的修改会立即保存到主存中,并通知其它线程读取的数据无效,重新在内存中读取,

   3.有序性   java通过volatile关键字来保证一定的有序性,volatile修饰的变量实际上是一个指令重排序的分界线,上部分代码和下部分代码都进行各自的指令重排序,但是上部分代码执行完了之后才会执行下部分代码。

 

volatile关键字:保证了有序性和可见性

      1.可见性

         下面这段代码,两个线程,当线程2把stop置为true时,线程1停止,但是,线程2把stop在自己的工作内存中置为true,又去干别的事情了,没来得及同步到主存,那这段代码就有问题了,线程1还会继续执行。所以stop变量应该用volatile修饰

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




//线程2
stop = true;

      2有序性   volatile的有序性是相对的,这个变量前面的代码先执行,然后这个变量后面的代码再执行,只能保证前面代码优于后面代码执行。但是前面代码可以进行自己的重排序,后面代码也可以进行自己的执行重排序。

    3volatile不能保证对变量的操作是原子性的

     

package com.xhx.java;

/**
 * xuhaixing
 * 2018/8/11 22:03
 **/
public class App {
    private volatile int i = 0;
    public void testIncrease(){
        i++;
    }


    public static void main(String[] args) {
        final App app = new App();
        for(int i = 0;i<10;i++){
            new Thread(()->{
                for (int j = 0;j<1000;j++){
                    app.testIncrease();
                }
                System.out.println(app.i);
            }).start();
        }
        
    }

}

java volatile关键字

看执行结果,volatile虽然保证了可见性,但是i++这个操作不具备原子性特征,读取变量值,+1,赋值,,很多线程可能都+1了或者已经读取了变量值正准备+1,在这个空隙之间,其他线程进行了i的赋值,这样有的线程+1得到的值就会和目前内存中的值一样,而没有真正的+1,,  volatile对变量的操作不具备原子性。

  要保证原子性可以用synchronized,lock或者atomic实现。

 

volatile使用场景:

    由于volatile不具备原子性,所以要摆脱原子性,才能使用volatile

 

1.标记状态变量,使状态变量修改后立即生效


volatile boolean flag = false;
 
while(!flag){
    doSomething();
}
 
public void setFlag() {
    flag = true;
}

   2.双重检查

         保证对象赋值后立即生效。

class Singleton{
    private volatile static Singleton instance = null;
     
    private Singleton() {
         
    }
     
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}