Java并发编程之Volatile知识整理

本文详细解析了Java中的volatile关键字,包括其定义、特性和在JVM层面的实现原理。阐述了volatile如何解决多线程环境下共享变量的一致性问题,以及其可见性和有序性的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

​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));  }  OrderAccess::storeload();// 四种内存屏障  inline void OrderAccess::loadload()   { acquire(); }inline void OrderAccess::storestore() { release(); }inline void OrderAccess::loadstore()  { acquire(); }inline void OrderAccess::storeload()  { fence(); }  //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变量的修改对其他线程立即可见。

二、读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” )】效果是一致的,能够禁止重排序,同时能够获取到最新的值。 

volati​le原理总结:

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更高,如果我们需要在多线程中访问共享遍历可以使用起来,但是注意它不保证操作原子性,修改的操作需要同步。

2022继续学习,一起进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

服务端技术栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值