Java面试题-线程篇-(6)volatile关键字

image.png

volatile是Java中的关键字,可以保证在多线程环境下,对共享变量的操作具有可见性和有序性,是一种轻量级的同步机制。之所以说它是轻量的同步机制是因为它满足了并发三大特性的可见性和有序性,但是它不能保证原子性。

一、volatile关键字具有以下作用:

1、保证可见性:当一个线程对共享变量进行了修改,那么另外的线程可以立即看到修改后的最新值。这是通过禁止指令重排序和立即刷新到主内存中实现的。

具体来说,当一个线程修改了一个volatile变量的值时,该线程会将该变量的值立即写入主内存中,并且会通知其他线程该变量已经发生了修改。其他线程在读取该volatile变量时,会从主内存中重新读取最新的值,而不是从本地工作内存中读取。这样就保证了多个线程能够立即看到共享变量的最新值,避免了数据不一致的问题。

在Java中,如果一个共享变量没有声明为volatile,可能会导致线程不可见问题。下面是一个示例代码:

public class SharedVariableDemo {
    private int count = 0;

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
    public static  class MyTest {
        public static void main(String[] args) throws InterruptedException {
            SharedVariableDemo sharedVariable = new SharedVariableDemo();
            Thread thread1 = new Thread(() -> {
                for (int i = 0; i < 5000; i++) {
                    sharedVariable.increment();
                }
            });
            Thread thread2 = new Thread(() -> {
                for (int i = 0; i < 5000; i++) {
                    sharedVariable.increment();
                }
            });
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            System.out.println("Count: " + sharedVariable.getCount());
        }
    }
}

执行结果:


image.png

在上面的代码中,count变量没有声明为volatile,两个线程分别对count变量进行了5000次自增操作。由于两个线程同时对count变量进行操作,如果一个线程修改了count的值,另一个线程可能无法立即看到最新的值,仍然读取的是旧的值,从而导致数据不一致的问题。

为了避免这种问题,可以将count变量声明为volatile,或者使用其他同步机制来保证多个线程对共享变量的操作有序进行。

private volatile int count = 0;

2、保证有序性:volatile关键字可以禁止指令重排序,从而保证对volatile变量的操作是有序的。当对一个volatile变量进行读/写操作时,该操作之前的代码必须已经执行完成,并且结果对后续的操作可见。也就是说,volatile关键字之前的代码不会被调整到其后面,volatile关键字之后的代码不会被调整到其前面。这是为了解决多线程环境下可能出现一些意想不到的执行顺序导致的错误。

具体来说,由于性能优化和处理器内部结构的限制,编译器和处理器可能会对指令序列进行重排序。这种重排序可能会打乱程序中原本的执行顺序,导致出现意想不到的结果。但是,如果一个变量被声明为volatile,那么编译器和处理器就会禁止对该变量的读写操作进行重排序,从而保证了对该变量的操作是有序的。

有序性在多线程环境下非常重要,因为多个线程可能同时对共享变量进行操作,如果这些操作没有保持有序性,就可能会出现数据不一致、条件不满足等问题。通过使用volatile关键字,可以禁止指令重排序,保证对共享变量的操作有序进行,从而避免了这些问题。

需要注意的是,volatile关键字只能保证对volatile变量的操作是有序的,无法保证整个程序的所有操作都有序进行。因此,在解决多线程环境下的问题时,还需要结合其他同步机制来保证整个程序的有序性。

二、volatile的实现机制和原理 通过前面对volatile的语义的使用的介绍,相信大家已经有了一个初步的了解,但是volatile为什么会实现这些特性呢?接来下我们就正式进入volatile底层原理的讲解。 1、内存屏障 (1)什么是内存屏障 内存屏障(memorybarrier)是一个CPU指令。这条指令可以确保一些特定指令的执行顺序,影响一些数据的可见性(可能是某些指令执行后的结果)。 插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。内存屏障另一个作用是强制更新一次不同CPU的缓存。 (2) 内存屏障的分类

1>LoadLoad屏障

Load1;
LoadLoad屏障;
Load2;

Load1和Load2代表两条读取指令。在Load2要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

2>StoreStore屏障

Store1;
StoreStore屏障;
Store2;

Store1和Store2代表两条写入指令。在Store2写入执行前,保证Store1的写入操作对其它处理器可见。

3>LoadStore屏障

Load1;
LoadStore屏障;
Store2;

在Store2被写入前,保证Load1要读取的数据被读取完毕。

4>StoreLoad屏障

Storel;
StoreLoad屏障;
Load2;

在Load2读取操作执行前,保证Store1的写入对所有处理器可见。StoreLoad屏障的开销是四种屏障中最大的。

2、实现机制 从代码的层面我们看不到voiatile的实现机制,因此我们需要从汇编指令的层次进行研究,我们查看一下第四章第一节代码中doStop方法的汇编指令

#可以看到此时有一个lock前经拾今
0x0000000003226f6e: lock add dword ptr [rsplh*putstatic stop
;-com.jicl.MyTest::doStopel (line 35)

lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),它会提供3个功能: 1> 确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成; 2> 强制将对缓存的修改操作立即写入主存,利用缓存一致性机制,并且缓存一致性机制会阻止同时修改由两个以上CPU缓存的内存区域数据。 3> 如果是写操作,它会导致其他CPU中对应的缓存行无效。

volatile的底层实现是通过插入内存屏障,但是对于编译器来说,发现一个最优布置来最小化插入内存屏障的总数几乎是不可能的,所以,JMM采用了保守策略。如下: 1> 在每一个volatile写操作前面插入一个Store Store屏障:保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中。

2> 在每一个volatile写操作后面插入一个StoreLoad屏障:避免volatile写与后面可能有的volatile读/写操作重排序

3> 在每一个volatile读操作后面插入一个LoadLoad屏障:禁止处理器把上面的volatile读与下面的普通读重排序

4> 在每一个volatile读操作后面插入一个LoadStore屏障:禁止处理器把上面的volatile读与下面的普通写重排序

3、实现原理 分析一下volatile是如何实现可见性和有序性的
(1) 可见性 如果对声明了volatile变量进行写操作时,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写会到系统内存。这一步确保了如果有其他线程对声明了volatile变量进行修改,则立即更新主内存中数据。但这时候其他处理器的缓存还是旧的,所以在多处理器环境下,为了保证各个处理器缓存一致,每个处理会通过探在总线上传播的数据来检查自己的缓存是否过期,当处理器发现自已缓存行对应的内存地址被修改了,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作时,会强制重新从系统内存把数据读到处理器缓存里。这一步确保了其他线程获得的声明了volatile变里都是从主内存中获取最新的。
(2) 有序性 Lock前缀指令实际上相当丁一个内存屏障(也成内存档),它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,176评论 5 469
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,190评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,232评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,953评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,879评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,177评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,626评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,295评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,436评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,365评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,414评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,096评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,685评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,771评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,987评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,438评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,032评论 2 341

推荐阅读更多精彩内容