在看单例模式的时候,遇到了一个新的关键字 volatile 。 看了很多介绍,不是很明白
1 在讲指令重排的时候,讲了一个例子,说 在 new 一个对象的时候,分为3个步骤,
<1> 创建内存,
<2>初始化对象,
<3>对象指针指向创建的内存
指令优化的时候,有可能会把 <3> 和 <2> 的顺序颠倒, 如果颠倒的话,那岂不是new对象
的时候就出错了? 但是我写程序从来没有new 出错过,想不明白
2 我查询的是C# 的指令重排与 volatile 关键字, 但是我看文章介绍的时候都会说是 jvm编译
指令的时候, jvm不是 java的东西吗? 但是我在C# 下可以打出 volatile 这个关键字,那C#
的编译器在编译的时候,到底有没有指令重排这种情况?
3 还有文章说 volatile 是 保证线程中更改主线程的值,立即更新到主线程的。 到底这个 volatile 是做什么的?
<2>初始化对象是一个内存操作,这可能是一个耗时操作,为了避免CPU在等待这个操作时停顿导致性能下降,编译器会调整指令顺序(开启优化)或者有些架构的CPU会乱序执行指令,也就是将<3>提前执行,这样的话,指针就指向了一个未初始化好的对象。外部得到的单例对象是一个未初始化好的对象,就会引发问题,并不是说new对象的时候出错。这只在多线程场景下才会有小概率出现。指令重排和CPU架构、编译器优化有关,和编程语言无关,不同编程语言都提供了一定的API或赋予关键字一定语义来保证CPU不乱序执行或者编译器不执行指令重排优化。
在C/C++中,volatile是为了提醒编译器在执行的单一线程内, volatile 访问不能被优化掉,但是volatile并不能保证数据是多线程安全的。其他语言也应当是一样的,但是不同语言可能赋予了volatile更多的语义
那再多线程的应用中,每个引用类型都要加这个关键字,保证不出错么? 那优化岂不是没用了?
@百鸟朝凤: volatile和多线程没有直接关系,除非你认为不能优化,否则没有必要加这个关键字。比如
a = 1;
a = 2;
a = 3;
编译器认为a最后等于3,前面两行代码是没有用的,就会把这两行代码直接忽略掉,正常来说这样的优化没有问题,但在某些场景下就有问题,常见于嵌入式开发。多线程安全不是靠volatile来保证的,需要依赖其他同步机制,比如锁。
PS:上面是基于C/C++的volatile回答的
@一罪: 那我看单例模式的时候,就要加上这个关键字,有这个必要么
@百鸟朝凤: 没有必要,单例模式最容易出错的就是多线程实例化的时候,而volatile无法保证多线程安全(不是原子操作),还是要采用同步机制,保证只有一个线程去做实例化。
@一罪: 哦哦,谢谢
@百鸟朝凤: 不客气,可以多用调试器看看程序执行流,看看volatile对程序执行流的影响,会理解的更深刻一点
volatile保证可见性和有序性。
可见性可以理解为:在多线程环境中,任何线程对共享变量的修改,其他线程都是可感知的。
有序性就是为了防止重排序,至于你说的那个new对象问题也只是理论上可能出现的,平时开发基本不可能遇到的。
类中的静态变量属于共享变量么?类在子线程实例化。
@百鸟朝凤: 简单说就是多个线程会对某个变量做修改,共享是对线程的执行方法而言的。