volatile 关键字(修饰变量)

volatile 关键字(修饰变量)

1. 含义

是一种比 sychronized 关键字更轻量级的同步机制,访问 volitile 变量时,不会执行加锁操作。

2. 作用

volatile 是一个类型修饰符(type specifier)。volatile 的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。

  1. 保证可见性
  2. 禁止指令重排序优化

指令重排序优化:普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序和程序代码中的执行顺序一致

3. 如何保证可见性

新值能立即同步到主内存,以及每次使用前立即从主内存刷新。

  • volatile 修饰的变量,是直接拿的主内存的值,就是说这个值永远是最新的,对其他线程是可见的。
  • 而访问非 volatile 变量时,每个线程都会从系统内存(主内存)拷贝变量到工作内存中,然后修改工作内存中的变量值,操控的变量可能不同。

4. 如何禁止指令重排序优化

volatile 通过设置 Java 内存屏障禁止重排序优化。

java 内存屏障 内存屏障也称为内存栅栏或栅栏指令,是一种屏障指令,它使CPU或编译器对屏障指令之前和之后发出的内存操作执行一个排序约束。 这通常意味着在屏障之前发布的操作被保证在屏障之后发布的操作之前执行。 java 的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合,完成一系列的屏障和数据同步功能。 (Load 指令(也就是从内存读取),Store指令 (也就是写入内存)。)

  • LoadLoad 屏障:对于这样的语句 Load1; LoadLoad; Load2 ,在 Load2 及后续读取操作要读取的数据被访问前,保证Load1 要读取的数据被读取完毕。
  • StoreStore 屏障:对于这样的语句 Store1; StoreStore; Store2 ,在 Store2 及后续写入操作执行前,保证 Store1 的写入操作对其它处理器可见。
  • LoadStore 屏障:对于这样的语句 Load1; LoadStore; Store2 ,在 Store2 及后续写入操作被刷出前,保证 Load1 要读取的数据被读取完毕。
  • StoreLoad 屏障:对于这样的语句 Store1; StoreLoad; Load2 ,在 Load2 及后续所有读取操作执行前,保证 Store1 的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能
volatile 做了什么

在一个变量被 volatile 修饰后,JVM 会为我们做两件事:

  1. 在每个 volatile 写操作前插入 StoreStore 屏障,在写操作后插入 StoreLoad 屏障。(StoreStore-写-StoreLoad
  2. 在每个 volatile 读操作前插入 LoadLoad 屏障,在读操作后插入LoadStore屏障。(LoadLoad-读-LoadStore

5. volatile 是不安全的

虽然 volatile 可见性保证了对 volatile 变量所有的写操作都能立刻反应到其他线程之中(即 volatile 变量在各个线程中都是一致的),但是 Java 里面的运算并非原子操作。只有是原子操作的 volatile 变量才是线程安全的,比如我们很常见的 变量++ 自增操作,在这个过程中,自增包括取数,加一,保存三个过程的操作,所以自增并不是原子性操作,使用 volatile 修饰的变量自增操作仍然是不安全的。

举个例子:
public class MyVolitile {

    private static volatile int count = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        count++;
                    }
                }
            }).start();
        }
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(count);
    }
    
}

结果每次都不一样,不一定等于 200。

6. volatile 不适用场景

由于 volatile 变量只能保证可见性,在不符合以下两条规则的运算场景中,我们仍然要通过加锁(使用 synchronized 或 java.util.concurrent 中的原子类)来保证原子性。

  • 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
  • 变量不需要与其他的状态变量共同参与不变约束

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏曌的晓痴

LeetCode - TinyURL的加密与解密

原题地址:https://leetcode-cn.com/problems/encode-and-decode-tinyurl/

13720
来自专栏曌的晓痴

LeetCode - 保持城市天际线

原题地址:https://leetcode-cn.com/problems/max-increase-to-keep-city-skyline/

9620
来自专栏Tech爬虫(公众号php_pachong)

视图view类-源码解析

视图类view主要用于页面内容的输出,模板调用等,用在控制器类中,可以使得控制器类把表现和数据结合起来。下面我们来看一下执行流程。

8010
来自专栏须臾之余

Java集合源码分析之LinkedList

前面一篇我们分析了ArrayList的源码,这一篇分享的是LinkedList。我们都知道它的底层是由链表实现的,所以我们要明白什么是链表?

8830
来自专栏Java识堂

你确定真正理解联合索引和最左前缀原则?

前文已经说了explain命令的大部分参数,接着图解: EXPLAIN 实战-1这篇文章把explain的key_len参数分享完,接着分享最左前缀原则,建立如...

15670
来自专栏代码编写世界

harris角点检测的简要总结

harris角点检测是一种特征提取的方法,而特征提取正是计算机视觉的一种重要手段。尽管它看起来很复杂,其实也是基于数学原理和简单的图像处理来实现的。 本文之前...

41720
来自专栏曌的晓痴

LeetCode - 二叉搜索树的范围和

原题地址:https://leetcode-cn.com/problems/range-sum-of-bst/

8130
来自专栏程序员开发者社区

Java常用问题排查工具

jstack能得到运行java程序的java stack和native stack的信息

9520
来自专栏曌的晓痴

LeetCode - 独特的电子邮件地址

LeetCode第929题,难度简单。三个半月之前的题目了,最近只能够选择周末做题,然后一次性把一周的题目都写完,然后每天回家定时发送

6220
来自专栏Tech爬虫(公众号php_pachong)

ThinkPHP上传文件

<form id="upload" method='post' action="__URL__/upload/" enctype="multipart/form...

15150

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励