一文搞懂volatile面试题

简介: 这篇文章是关于Java关键字volatile的详细介绍和分析,volatile是多线程访问共享变量时保证一致性的方案,性能优于synchronized,但不保证操作原子性,需要同步处理。

volatile定义:

    volatile是Java提供的关键字,用来实现变量在多个线程之间共享的机制。用来修饰成员属性,final不能和volatile一起使用。

    如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。它有线程可见性,禁止指令重排序的特性。 

为何需要volatile?

在Java8规范文档中第8节中给了我们答案。

上面的意思就是说volatile是用于多个线程并发安全访问共享变量的机制。由于Jvm中每个线程都有自己的工作内存,并且操作变量都是操作工作内存,这种情况下如果多个线程对同一个变量操作会导致数据不一致,因此Jvm提供了volatile来解决这种问题。

volatile特性:

一、可见性

volatile是Java语言的一个关键字,可见性的能力是在jvm层面实现的。

可见性保证某一个线程对volatile修饰的变量的修改对其他线程立即可见。

二、有序性:禁止指令重排序

当程序执行到volatile变量的读或写时,在其前面的操作肯定全部已经执行完毕,且结果已经对后面的操作可见;在其后面的操作肯定还没有执行。

volatile实现原理:

volatile关键字在jvm层面会插入汇编语句lock指令,下面是openjdk8源码,在对volatile修饰的变量操作时,会插入内存屏障。

一、写volatile源码实现

if (cache->is_volatile()) {
  if (tos_type == itos) {
    obj->release_int_field_put(field_offset, STACK_INT(-1));
  } else if (tos_type == atos) {
    VERIFY_OOP(STACK_OBJECT(-1));
    obj->release_obj_field_put(field_offset, STACK_OBJECT(-1));
    OrderAccess::release_store(&BYTE_MAP_BASE[(uintptr_t)obj >> CardTableModRefBS::card_shift], 0);
  } else if (tos_type == btos) {
    obj->release_byte_field_put(field_offset, STACK_INT(-1));
  } else if (tos_type == ltos) {
    obj->release_long_field_put(field_offset, STACK_LONG(-1));
  } else if (tos_type == ctos) {
    obj->release_char_field_put(field_offset, STACK_INT(-1));
  } else if (tos_type == stos) {
    obj->release_short_field_put(field_offset, STACK_INT(-1));
  } else if (tos_type == ftos) {
    obj->release_float_field_put(field_offset, STACK_FLOAT(-1));
  } else {
    obj->release_double_field_put(field_offset, STACK_DOUBLE(-1));
  }
  //这是实现写volatile变量的关键,通过storeload屏障实现
  OrderAccess::storeload();
  • jvm层四个内存屏障定义

inline void OrderAccess::loadload()   { acquire(); }
inline void OrderAccess::storestore() { release(); }
inline void OrderAccess::loadstore()  { acquire(); }
inline void OrderAccess::storeload()  { fence(); }
  • storeLoad内存屏障实现
//storeload屏障的实现
inline void OrderAccess::fence() {
  if (os::is_MP()) {
    // always use locked addl since mfence is sometimes expensive
#ifdef AMD64
    __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
    __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
  }
}

通过jitwatch工具查看修改volatile变量时汇编指令如下:就是上面storeload屏障实现的。

下面看看Storeload屏障的作用:

总的来说Storeload屏障保证对volatile变量的修改对其他线程立即可见。

下次面试官问就知道了。

image.png

二、读volatile源码实现

if (cache->is_volatile()) {
  if (tos_type == atos) {
    VERIFY_OOP(obj->obj_field_acquire(field_offset));
    SET_STACK_OBJECT(obj->obj_field_acquire(field_offset), -1);
  } else if (tos_type == itos) {
    SET_STACK_INT(obj->int_field_acquire(field_offset), -1);
  } else if (tos_type == ltos) {
    SET_STACK_LONG(obj->long_field_acquire(field_offset), 0);
    MORE_STACK(1);
  } else if (tos_type == btos) {
    SET_STACK_INT(obj->byte_field_acquire(field_offset), -1);
  } else if (tos_type == ctos) {
    SET_STACK_INT(obj->char_field_acquire(field_offset), -1);
  } else if (tos_type == stos) {
    SET_STACK_INT(obj->short_field_acquire(field_offset), -1);
  } else if (tos_type == ftos) {
    SET_STACK_FLOAT(obj->float_field_acquire(field_offset), -1);
  } else {
    SET_STACK_DOUBLE(obj->double_field_acquire(field_offset), 0);
    MORE_STACK(1);
  }
}

cache->is_volatile(),表示如果变量i被volatile修饰,那么为true,接着获取变量i的值,操作由xxx(类型) _field_acquire方法实现。\

以int为例,来看看int_field_acquire方法的实现,这个方法在oop.inline.hpp中:


inline jint oopDesc::int_field_acquire(int offset) const {

return OrderAccess::load_acquire(int_field_addr(offset)); }

我们看到,内部调用了acquire方法,该方法在不同的系统环境中有不同的实现,我们来看看在linux中的实现,该实现在orderAccess_linux_x86.inline.hpp中:


inline jint OrderAccess::load_acquire(volatile jint* p) { return

*p; }

我们可以看到第一个参数加了关键字volatile,这一这里的volatile属于C++的关键字,该关键字在C++中的作用如下:

  volatile是一种类型修饰符,被volatile声明的变量表示随时可能发生变化,每次对变量的读取,都会从内存中重新加载。并且编译器对操作该变量的代码不再进行优化,比如不再使用乱序优化,这也就是内存屏障的作用。

  这里我们可以看到读volatile就是使用的C++的volatile关键字控制的,并没有手动插入编译器屏障。我们也可以发现,实际上C++的volatile关键字和手动插入的编译器屏障【_ asm _ _ volatile _ ( " " : : : “memory” )】效果是一致的,能够禁止重排序,同时能够获取到最新的值。

volatile原理总结:

a) 读volatile:基于c++的volatile关键字,每次从主存中读取。

b) 写volatile:基于c++的volatile关键字和 lock addl指令的内存屏障,每次将新值刷新到主存,同时其他cpu缓存的值失效。

c) C++的volatile禁止对这个变量相关的代码进行乱序优化(重排序),也就具有内存屏障的作用了,另外Linux内核也可以手动插入内存屏障:_ asm _ _ volatile _ ( " " : : : “memory” )

d) 上面分析的是jvm层面的实现,其实可见性和有序性底层依赖cpu的特性,比如缓存一致性协议,总线锁等,这些知识需要对硬件知识有理解,笔者对这块了解的不够,有兴趣的读者可以去研究研究。

volatile带来的问题:

一、伪共享问题

修改volatile修饰的变量时,会强制刷新到主存。并且使��他工作线程中的变量缓存行失效,这样保证可可见性,但是会产生伪共享问题,计算机缓存系统,最小缓存单位是缓存行,如果多个变量存在同一个缓存行,如果修改有volatile修饰的变量,则修改时这整个缓存行就会失效。这个时候所有变量都重新从主存获取。影响整体性能。

Jvm虚拟机层面解决volatile带来的缓存行伪共享的方案。Jdk8提供了@sun.misc.Contended注解,解决伪共享问题,自动为volatile变量填充满缓存行,ConcurentHashMap中count计数器就用到该技术。

@sun.misc.Contended static final class CounterCell {
   
       volatile long value;    CounterCell(long x) {
   
    value = x; }}

总结:

本次分析了volatile的功能,特性以及在Jvm层面的实现原理,volatile是在多线程访问共享变量时一致性访问的方案,它的性能比Synchronized更高,如果我们需要在多线程中访问共享遍历可以使用起来,但是注意它不保证操作原子性,修改的操作需要同步。

相关文章
|
4月前
|
存储 安全 Java
Java面试题:深入探索Java内存模型,Java内存模型中的主内存与工作内存的概念,Java内存模型中的happens-before关系,volatile关键字在Java内存模型中的作用
Java面试题:深入探索Java内存模型,Java内存模型中的主内存与工作内存的概念,Java内存模型中的happens-before关系,volatile关键字在Java内存模型中的作用
38 1
|
12天前
|
存储 缓存 Java
大厂面试高频:Volatile 的实现原理 ( 图文详解 )
本文详解Volatile的实现原理(大厂面试高频,建议收藏),涵盖Java内存模型、可见性和有序性,以及Volatile的工作机制和源码案例。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:Volatile 的实现原理 ( 图文详解 )
|
4月前
|
缓存 安全 算法
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
49 0
|
6月前
|
存储 缓存 安全
面试官:说说volatile底层实现原理?
面试官:说说volatile底层实现原理?
500 5
面试官:说说volatile底层实现原理?
|
3月前
|
缓存 安全 Java
面试官:说说volatile应用和实现原理?
面试官:说说volatile应用和实现原理?
46 1
|
3月前
|
缓存 Java
【多线程面试题二十三】、 说说你对读写锁的了解volatile关键字有什么用?
这篇文章讨论了Java中的`volatile`关键字,解释了它如何保证变量的可见性和禁止指令重排,以及它不能保证复合操作的原子性。
|
4月前
|
缓存 安全 Java
Java面试题:解释volatile关键字的作用,以及它如何保证内存的可见性
Java面试题:解释volatile关键字的作用,以及它如何保证内存的可见性
77 4
|
4月前
|
设计模式 缓存 安全
Java面试题:工厂模式与内存泄漏防范?线程安全与volatile关键字的适用性?并发集合与线程池管理问题
Java面试题:工厂模式与内存泄漏防范?线程安全与volatile关键字的适用性?并发集合与线程池管理问题
59 1
|
4月前
|
存储 缓存 安全
Java面试题:介绍一下jvm中的内存模型?说明volatile关键字的作用,以及它如何保证可见性和有序性。
Java面试题:介绍一下jvm中的内存模型?说明volatile关键字的作用,以及它如何保证可见性和有序性。
37 0
|
4月前
|
设计模式 缓存 安全
Java面试题:详解单例模式与内存泄漏?内存模型与volatile关键字的实操?并发工具包与并发框架的应用实例
Java面试题:详解单例模式与内存泄漏?内存模型与volatile关键字的实操?并发工具包与并发框架的应用实例
33 0
下一篇
无影云桌面