Java成神之路——volatile是什么?

volatile 是什么?

java虚拟体提供的轻量级同步机制,可以保证内存可见性,不能保证原子性,禁止指令重排序

java内存模型

java memory model。java内存模型一种抽象概念或规范,通过这组规范定义了程序访问变量(实力字段,静态字段,数组元素等)的访问方式。

JVM运行程序的实体是线程,每个线程在创建是JVM都会为其创建一个工作内存或称栈空间,工作内存是每个线程私有的数据区域,而java内存模型中规定变量都是存储在主内存中,主内存是线程共享的内存区域,所有线程都可以访问,但线程对变量的操作必须在工作内存中进行,首先要将变量从主内存中拷贝到自己的工作内存中,对变量进行操作,操作完成后写会主存中,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中变量的副本,线程之间无法访问对方的工作内存,线程间的通信(值的传递)必须通过主内存来完成。

在这里插入图片描述

内存可见性测试
class MyData{
    int number = 0;

    public void addNumber(){
        this.number = 10;
        System.out.println("number值改为10");
    }
}

public class TestVolatile {
    public static void main(String[] args) {
        MyData myData = new MyData();
        // 开启一个新的线程延时3毫秒后改变number的值
        new Thread(()->{
            try {
                Thread.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.addNumber();
            System.out.println("线程"+Thread.currentThread().getName()+"修改了myData的number值");
        },"线程A").start();
		// 如果myData的number一直为0则程序死循环不结束
        while(myData.number == 0){
        }
        System.out.println("main线程结束,myData.number != 0 ");
    }
}

程序运行结果出乎意料,在线程A修改number的值后并没有正常的结束,由于while条件判断执行的很快,没有时间去主存中读取已经被线程A修改后的number值,一直在判断的是main线程工作内存中的number的副本。(可以在while中添加延时操作,这样main线程有机会重新加载主存中的值也可以结束),正常情况下JMM不能保证内存可见性,线程A修改变量number,对于main线程不是实时可见的。在number成员变量前添加 volatile 修饰 保证内存可见,程序即可正常退出。

不保证原子性测试

volatile并不能保证变量操作的原子执行,只能保证变量的修改时其他线程实时可见。(++操作不是原子的),如果想要保证原子性,可以使用synchronized、ReentrantLock或AtomicInteger

class MyData2 {
    volatile int number = 0;

    public void addNumber() {
        this.number++;
    }
}

public class TestVolatileAtomic {
    public static void main(String[] args) {
        MyData2 myData = new MyData2();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    myData.addNumber(); }
            }, String.valueOf(i)).start();
        }
        // 如果有多余的线程,main线程与gc共2个
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(myData.number);
    }
}

为什么volatile可以保证内存可见?

Java内存模型也规定了工作内存与主内存之间交互的协议,定义了8种原子操作

  1. lock:将主内存的变量锁定,为一个线程所独占。
  2. unlock:将lock加的锁定解除,此时其他线程可以有机会访问此变量。
  3. read:将主内存中的变量值读到工作线程中。
  4. load:将read读取到的值保存到工作内存中的变量副本中。
  5. use:将值传递给线程的代码执行引擎。
  6. assign:将执行引擎处理返回的值重新赋值给变量副本。
  7. store:将变量副本的值存储到主内存中。
  8. write:将store存储的值写入到主内存的共享变量中。

lock、unlock是同步锁所产生的(如:synchronized、com.util.concurrent中的原子类)。

volatile 的作用就是保证【read、load、use】与【assign、store、write】这每组里面的操作都是有序的,强制,三条指令一起执行,实际意义就是,每次使用变量都从主内存中读取,每次修改完变量都立即刷新回主存中。

volatile禁止指令重排

指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。

int a = 10; // 1
int b = 20; // 2
int c = a*b; // 3

1,2的执行顺序,并不影响步骤3的执行结果,程序在运行期间可能根据情况对指令进行重排序提升运行效率。

重排序带来的问题

一个经典的案例就是双重检测锁实现单例模式。

public class Singleton {
  private static Singleton instance = null;
  private Singleton() { }
  public static Singleton getInstance() {
     if(instance == null) {
        synchronzied(Singleton.class) {
           if(instance == null) {
               instance = new Singleton();  //非原子操作
           }
        }
     }
     return instance;
   }
}

由于jvm可能会对非原子操作进行重排序,这就会带来一些问题。

instance = new Singleton();

对象的new操作并不是原子性的。实际上new操作可抽象为三个操作指令

memory =allocate();
instance =memory; 
ctorInstance(memory);
  1. 分配内存空间
  2. instance引用指向分配的内存空间
  3. 使用分配的内存初始化对象

如果jvm对new操作进行了指令重排可能会是这样

memory =allocate(); 
instance =memory; 
ctorInstance(memory)
  1. 分配内存空间
  2. instance引用指向分配的内存空间
  3. 初始化对象

这就会带了一个问题,在多线程中,线程A第一次获取单实例执行new Singleton操作,instace为null,在执行memory =allocate();instance =memory;操作后发生线程的切换,线程B获取单实例,但这时instance引用指向了分配的内存已经不为空,但内存的初始化工作还没有执行,这就让线程B拿到了一个未初始化完成的对象,给instance添加volatile修饰禁止指令重排问题解决。

总结

volatile的关键作用

  1. 保证多线程环境中,变量在线程之间的实时可见(在AQS,原子变量中都有使用)。
  2. 防止指令重排序。

切记 volatile无法保证原子性
交流Q群 892480622

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值