当一个变量被定义成 volatile 之后,它将具备两项特性:

第一项 是保证此变量对所有线程的可见性,这里的 可见性 是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。新值能够 立即得知 是因为每次使用变量之前都要先刷新。

  而 普通变量 并不能做到这一点,普通变量的值在线程间传递时均需要通过 主内存 来完成。
比如, 线程A 修改一个普通变量的值,然后 向主内存进行回写 ,另外一条 线程B线程A回写完成 了之后再对主内存进行读取操作, 新变量 值才会 对线程B可见

Java内存模型 是通过在 变量修改后新值同步回主内存 ,在变量 读取前主内存刷新变量值 这种依赖主内存作为传递媒介的方式来实现 可见性 的,无论是普通变量还是volatile变量都是如此。

普通变量volatile 变量的区别是, volatile 的特殊规则保证了 新值立即同步到主内存以及每次使用前立即从主内存刷新 。因此我们可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。

volatile不安全 因为Java里面的运算操作符并非原子操作,这导致 volatile 变量的运算在 并发下 一样是 不安全

第二项 使用 volatile 变量的第二个语义是 禁止指令重排序优化 ,普通的变量仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致。




1.原子性(Atomicity)

  由Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store和write这六个,我们大致可以认为,基本数据类型的访问、读写都是具备原子性的(例外就是long和double的非原子性协定,读者只要知道这件事情就可以了,无须太过在意这些几乎不会发生的例外情况)。如果应用场景需要一个更大范围的原子性保证(经常会遇到),Java内存模型还提供了lock和unlock操作来满足这种需求,尽管虚拟机未把lock和unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐式地使用这两个操作。这两个字节码指令反映到Java代码中就是同步块——synchronized关键字,因此在synchronized块之间的操作也具备原子性。

2.可见性(Visibility)

  可见性就是指当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改。上文在讲解volatile变量的时候我们已详细讨论过这一点。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此。普通变量与volatile变量的区别是,volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此我们可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。除了volatile之外,Java还有两个关键字能实现可见性,它们是synchronized和final。同步块的可见性是由 “对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)” 这条规则获得的。而final关键字的可见性是指:被final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那么在其他线程中就能看见final字段的值。

3.有序性(Ordering)

  Java内存模型的有序性在前面讲解volatile时也比较详细地讨论过了,Java程序中天然的有序性可以总结为一句话:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句是指“线程内似表现为串行的语义”(Within-Thread As-If-SerialSemantics),后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象。Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile关键字本身就包含了禁止指令重排序的语义,而synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入。

分类: Java

毛巳煜

高级软件开发全栈架构师

工信部备案号:辽ICP备17016257号-2