java关键字volatile

文章目录

    • 引言
    • 概念
      • 1、作用与可用范围
      • 2、简单流程
      • 3、原子性
      • 应用
        • 单例模式的双重检查
      • 实现原理

引言

在java语言编程中,我们会使用到很多修饰符(也可以说是关键字),比如说public、class、final、static等,他们都是各自的作用,今天我们来介绍介绍volatile这个修饰符。

概念

1、作用与可用范围

volatile只能用于修饰变量,不能用于修饰方法或者类等。那么此时这个变量就拥有了两层语义了。

  • 当前变量对所有线程可见
    这里的所有线程指的是操作或者使用这个变量的线程
  • 禁止指令重排序

2、简单流程

  • A线程从主内存中读取了i的值到A工作内存中
  • B线程修改B工作内容中的i为j,并更新到主内存中此时主内存值为j
  • 此时A的工作内存中的i值失效
  • A同步更新主内存中的j,此时A工作空间的值为j

3、原子性

此关键字不会保证原子性
举个简单的例子:

// 定义一个变量
private volatile static int i = 0;
/**
 - 创建新的线程执行i自增的结果
 */
public static void testAdd() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    count++;
                }
                countDownLatch.countDown();
            }).start();
        }
        countDownLatch.await();
        System.out.println(count);
}

以上代码执行完理论上来说应该是 1000*10=10000,但是实际执行完后没有10000,就是因为volatile不会保证原子性导致的

  • A线程最开始拿到0并加载到工作内存,执行了自增操作
  • 此时CPU切换至B线程,B拿到的也是0,因为A线程并没有刷新到主内存中
  • B线程执行了自增,并且更新到主内存,此时主内存为1
  • 此时CPU又切换回A线程,并且把1也刷新至主内存
  • 理论结果值应该为2,但是实际结果为1

应用

单例模式的双重检查

这里主要就是禁止指令重排序。因为编译器和处理器会优化程序性能,进行指令重排序。
比如说我们在单例模式中会有这样一段代码

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

获取单例模式可以分为三步
1、分配内存
2、初始化对象
3、变量指向内存地址
如果这里不使用volatile,在2、3步可能会指令重排序,导致最终拿到的对象可能是还没有初始化完成的对象

实现原理

1、确保指令不会重排序
2、强制刷新至主内存
3、其他工作内存的缓存失效

你可能感兴趣的:(java,java,开发语言)