七、详解Volatile

关于volatile

volatile是Java提供的一种轻量级的同步机制,它主要有两个特性:保证变量的可见性和禁止指令重排序。  

1. 保证变量的可见性:在Java中,为了提高效率,每个线程都会有自己的工作内存(可以理解为CPU的高速缓存),线程对变量的所有操作都会在工作内存中进行,然后再同步回主内存。如果一个变量被volatile修饰,那么当一个线程修改了这个变量后,新的值会立即同步回主内存,当其他线程需要读取这个变量时,会直接从主内存中读取,而不是从工作内存,这样就保证了变量的可见性。

 2. 禁止指令重排序:在执行程序时,为了提高性能,编译器和处理器可能会对指令做重排序。但是,如果对volatile变量的读写,Java内存模型会禁止指令重排序。具体来说,写入volatile变量的操作会在读写操作之前执行,读取volatile变量的操作会在读写操作之后执行。

关于指令重排

在Java中,为了提高程序运行效率,编译器和处理器可能会对指令进行重排序。这种重排序在单线程环境下是没有问题的,因为它不会改变程序的执行结果。但在多线程环境下,指令重排序可能会导致严重的问题。  

指令重排主要包括以下三种类型:  

1. 编译器优化的重排:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。  2. 指令级并行的重排:现代处理器采用了指令级并行技术(Instruction-Level Parallelism,简称ILP)来提升性能。ILP会在CPU内部对指令进行动态重排。  

3. 内存系统的重排:由于处理器使用缓存和读/写缓冲区,这也可能导致处理器看到的内存顺序与实际的顺序不同。

volatile原理

volatile底层原理就是利用内存屏障

在写volatile的时候,在写后,会进行写屏障。这样一来,在对volatile变量赋值之后,就会执行写屏障,将新的数据刷到主存上。

在读volatile的时候,在读前,就进行读屏障。这样一来,在对volatile变量进行读取的时候,就会执行读屏障,将主存上的数据读取过来。

同时,在写屏障执行之前,jvm确保之前的代码不会出现在写屏障之后。

volatile修饰的变量只能保证在当前线程内,变量的数据是主存上最新的。但是不能保证线程之间的互斥性。当一个线程在读取或者写入volatile变量的时候,通过读写屏障确保数据最新,但是当前线程不能控制另一个线程修改volatile。

DCL

常见的饱汉式单例模式我们在业务中经常会用,一个完美的代码如下:
 

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

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

 这里面可以发现instance变量用volatile修饰了。那么为什么要用valatile修饰呢?

我们可以看一下没有volatile变量修饰的字节码文件是什么样的

 0 getstatic #3 <com/VolatileTest.instance : Lcom/VolatileTest;>
 3 ifnonnull 37 (+34)
 6 ldc #4 <com/VolatileTest>
 8 dup
 9 astore_0
10 monitorenter
11 getstatic #3 <com//VolatileTest.instance : Lcom/VolatileTest;>
14 ifnonnull 27 (+13)
17 new #4 <com/VolatileTest>
20 dup
21 invokespecial #5 <com/VolatileTest.<init> : ()V>
24 putstatic #3 <com/VolatileTest.instance : Lcom/VolatileTest;>
27 aload_0
28 monitorexit
29 goto 37 (+8)
32 astore_1
33 aload_0
34 monitorexit
35 aload_1
36 athrow
37 getstatic #3 <com/VolatileTest.instance : Lcom/VolatileTest;>
40 areturn

可以看到 instant = new VolatileTest()这段java代码,在字节码文件中其实是对应多行指令:

1、new 命令。创建对象的实例,但是此时只是为对象分配了空间

2、invokespecial命令。真正执行构造方法

3、putstatic命令。将实例对象赋值给静态变量。

在极端情况下,putstatic和invokespecial可能发生指令重排。如果发生重排,就导致当前instance对象还没有执行构造方法,就将一个空的盒子返回了。

因此,如果其他线程来判断 instance==null 的时候就会返回false。从而导致其他线程拿到了一个空盒的对象,如果利用这个对象去进行操作,那么就会有问题。

而如果加上了volatile修饰,因为有内存屏障,上述指令就不会重排,也就不会出现问题

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值