并发之美------底层原理之volatile

是什么

Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排它锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁还要更加方便。如果一个字段被声明程volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。

为什么

1.在并发编程中会存在多个线程操作同一个共享变量的情况,因为是并发编程,所以是多个线程同时执行
2.那么为什么要用它呢?
volatile是轻量级的synchronized,它在多处理器并发中保证了共享变量的“可见性”。可见性的意识是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换调度。

实现原理

Java代码如下:

instance=new Singleton();                 //instance是volatile变量

转变成汇编代码,如下:

0x01a3deld:movb $0*0,0*1104800(%esi);0x01a3de24;lock addl $0*0,(%esp);

解析:
从上面的汇编代码可以看到,存在lock前缀
lock前缀的指令在多核处理器下会引发两件事情:
(1)将当前处理器缓存行的数据写回到系统内存
(2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效---------因为无效所以其他CPU想要使用这个数据时就需要从内存中重新拉取数据
操作步骤如图所示:
在这里插入图片描述
解释:
1.给b加了volatile关键字修饰后,线程1对b做了修改,然后会立即更新内存中的值
2.线程2通过嗅探发现自己的副本已经过期了,然后重新从内存中拿到b=true的值(缓存一致性协议)

使用示例----单例模式为什么要用volatile关键字

线程安全的单例模式常见写法是双重检查加锁,代码如下:

class Singleton{

    private volatile static Singleton singleton;



    private Singleton(){}       

    public static Singleton getInstance(){       

        if(singleton == null){                  // 1

            synchronized(Singleton.class){      // 2

                if(singleton == null){          // 3

                    singleton = new Singleton(); // 4

                }

            }

        } 

        return singleton;           

    }

}

其他具体的这里就不做解释了,下面一起来看一下为什么要加volatile呢?
首先理解new Singleton()做了什么,步骤如下:
1.看class对象是否加载到内存中,如果没有就先加载class对象
2.分配内存空间,初始化实例
3.调用构造函数
4.返回地址给引用
而CPU为了优化程序,可能会进行指令重排序,如果打乱3,4这两个步骤,导致实例内存还没分配,就被使用了,那么会有什么bug吗?
其实如果是单线程执行是没有问题的,但是如果变成多线程执行就会有问题,问题如下:
线程A执行到new Singleton(),开始实例化实例对象,由于存在指令重排序,这次new操作,先把引用赋值了,还没有执行构造函数,这是时间片结束了,切换到线程B执行,线程B调用new Singleton()方法,发现引用不等于null,就直接返回引用地址了,然后线程B执行了一些操作,就可能导致线程B使用了还没有被初始化的变量,那么加上volatile之后,就保证new不会被指令重排序,具体原理稍后做解释!

volatile重排序规则表
在这里插入图片描述
所以实例的第四个步骤为:返回地址给引用 ,此时是对volatile变量进行写操作,所以根据上表,第一操作为任何操作都不可以进行重排序,所以3 4两个步骤不进行重排序,所以多线程情况下就不会出现问题了!

### 回答1: Volatile是一种Java中的关键字,用于标识变量是易变的,即该变量的值可能会在不同的线程中发生改变。Volatile底层原理涉及到Java内存模型。 Java内存模型定义了线程如何与内存交互以及线程之间如何共享内存。Java内存模型将内存分为主内存和线程工作内存。主内存是所有线程共享的内存区域,而线程工作内存是每个线程独立拥有的内存区域。 当一个线程访问一个volatile变量时,它会从主内存中读取最新的值。而当一个线程更新一个volatile变量时,它会将新的值立即写入主内存中。这保证了所有线程对volatile变量的读写操作都是可见的。 此外,volatile还具有禁止指令重排序的作用。在多线程并发编程中,编译器为了提高程序执行效率可能会对指令顺序进行重排序,但是这种重排序可能会导致并发问题。使用volatile可以禁止编译器对volatile变量的指令进行重排序,保证了程序的正确性。 总之,volatile底层原理是基于Java内存模型的,它保证了多线程环境下对volatile变量的可见性和禁止指令重排序的特性。 ### 回答2: Volatile是Java中的关键字之一,用于修饰变量,主要用于多线程编程中,以保证线程间变量的可见性和顺序性。 Volatile底层原理主要是通过内存屏障(Memory Barrier)和禁止重排序来实现的。内存屏障是一种CPU指令,能够强制刷新处理器缓存并保证读/写操作顺序的一致性。当一个线程修改了一个被volatile修饰的变量的值时,会立即将该值刷新到主内存,并通知其他线程对对应变量的缓存失效,强制其他线程从主内存重新读取最新值。 此外,volatile还可以禁止指令重排,保证代码的有序执行。在有volatile修饰的变量之前的指令一定会在其后的指令之前执行。这样可以避免了由于指令重排导致的数据不一致问题。 总之,Volatile底层原理主要通过内存屏障以及禁止指令重排来保证线程间变量的可见性和顺序性。它能够确保一个变量在多个线程之间的可见性,尤其用于一个线程修改了变量值时,其他线程能够立即感知到变量的变化,并从主内存中重新读取最新值,从而避免了线程间数据不一致的问题。同时,它还通过禁止指令重排,保证了代码的有序执行,避免了由于指令重排导致的逻辑错误。因此,在多线程编程中,合理使用Volatile关键字能够确保程序的正确性和稳定性。 ### 回答3: Volatile是Java中的关键字,用于修饰变量。它的底层原理是通过禁止线程内部的缓存变量副本,直接访问主存中的变量值,保证了多线程环境中的可见性和有序性。下面详细解释其底层原理。 在多线程环境下,每个线程都有自己的工作内存(线程的私有内存),存放变量的副本。由于性能原因,线程在执行操作时,通常会先将变量从主存中读取到工作内存中进行操作,然后再将修改的结果写回主存。这种操作称为“读写操作的优化”。 当一个变量被volatile修饰时,它的读写操作会具有特殊的语义。当一个线程对volatile修饰的变量进行写操作时,它会首先将值写入工作内存,然后立即刷新到主存中,并且通知其他线程该变量的值已经被修改。而当一个线程对volatile变量进行读操作时,它会立即从主存中读取最新的值,并且在读之前使自己的工作内存失效,以保证读操作获取的是最新值。 这种特殊的语义使得volatile能够保证多线程环境下的可见性和有序性。通过禁止线程内部的缓存变量副本,保证了每个线程对volatile变量的读写操作都是基于主存中最新的值,从而避免了数据不一致的问题。同时,由于读操作会使工作内存失效,写操作会立即刷新到主存,保证了变量的修改对其他线程的可见性和顺序性。 总结起来,volatile底层原理是通过禁止线程内部的变量副本,直接访问主存中的变量值,保证了在多线程环境下的可见性和有序性。它对于一些简单的变量操作可以替代锁,同时也可以用于线程间的通信,但并不能保证原子性。因此,在使用volatile时,需要根据具体的场景和需求来判断是否合适。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值