JMM内存模型 volatile关键字解析

简介: JMM内存模型 volatile关键字解析

对于多线程等等的各种操作,相比各位都了然于胸,现在我们来介绍一下更底层一点点的JMM内存模型,其实也是一个很简单的理想的内存模型

注意与JVM的内存模型区分

多线程内存模型主要是基于CPU缓存搭建起来的

这里就区分工作内存和主内存了

我们线程操作的其实是主内存的一个副本,多线程每个线程操作结束了之后需要刷回主内存

volatile关键字

我们知道volatile主要的两个功能就是

1.保证内存中的变量可见性

2.禁止指令重排序

下面我们来介绍一些关于volatile关键字以及高并发的内容

我们知道,如果两个线程同时操作一个变量,我们这里就称之为线程a和线程b,a线程假设操作了主内存的一个变量,另一个线程能感知到吗?

答案是不能,因为两者始终操作的是其工作内存中的变量副本而已.

如果这里我给共享的变量加入一个volatile关键字修饰,这里就b线程就能感知到工作内存中的变量变化了,这是为什么呢?

请听我慢慢解释

首先我们需要先了解一下经典的原子操作

然后我们来谈谈另一个线程是怎么感知到的

说到这里就不得不提我们的MESI(缓存一致性)协议了

这里就是线程a一修改主内存中的变量,其实这个修改是通过总线传输到主内存的

这里线程b就是通过总线嗅探机制,一直在监听总线,当他发现这里总线中有我的属性被修改了

这里我们的b线程对应的变量的属性就直接设置为I(无效),当下次线程b需要使用这个变量的时候

哦,他就只能取主内存里面再去刷入工作内存了

那么我们的硬件协议又是怎么让缓存一致性协议生效的呢?

其本质就是在其底层的汇编代码前面加上了lock前缀

注意这里是修改完直接就同步回主内存,主打一个即时性

关于这里的指令重排序,我们也来谈一谈

为什么指令重排序这里的指令重排序

主要是为了加快程序性能而产生的

遵循 happens before 和 as is serial 原则

本质上就是在下面一条语句依赖于上面一条语句的时候

不会执行指令重排序,不影响依赖关系就随便排序

注:java 在执行代码之前会看看语法树前后有没有相互依赖

下面我展示部分原则

懒汉模式出现的对象半初始化问题

我们知道懒汉模式这里会使用双重校验锁

我拿出了其两行字节码指令

假设这里的putstatic在init之前

这里刚刚putstatic之后,cpu就调度到另一个线程了

这里判断已经不是空了,直接拿来使用,就会发生意想不到的问题

假设我这里a开了一个账户充6000块,然后直接去消费了

结果消费的时候发现账户的前不翼而飞了,所以这里的指令重排序问题是一个大的问题

常见的几种内存屏障

我们都知道volatile关键字是底层实现其实是一些内存屏障来实现的

我这里贴出几种常见的内存屏障

然后我们可以查看一下Java具体是怎么实现的

我们以 openjdk8 根路径 jdk\src\hotspot\share\interpreter\zero 路径下的 bytecodeInterpreter.cpp 文件中,处理 putstatic 指令的代码:

先进行判断是否有volatile修饰的实例

然后判断是对于不同的修饰类型进行操作

CASE(_putstatic):
    {
          // .... 省略若干行 
          // Now store the result 现在要开始存储结果了
          // ConstantPoolCacheEntry* cache;     -- cache是常量池缓存实例
          // cache->is_volatile()               -- 判断是否有volatile访问标志修饰
          int field_offset = cache->f2_as_index();
          // ****重点判断逻辑**** 
          if (cache->is_volatile()) { 
            // 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) {// byte类型赋值
              obj->release_byte_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == ltos) {// long类型赋值
              obj->release_long_field_put(field_offset, STACK_LONG(-1));
            } else if (tos_type == ctos) {// char类型赋值
              obj->release_char_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == stos) {// short类型赋值
              obj->release_short_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == ftos) {// float类型赋值
              obj->release_float_field_put(field_offset, STACK_FLOAT(-1));
            } else {// double类型赋值
              obj->release_double_field_put(field_offset, STACK_DOUBLE(-1));
            }
            // *** 写完值后的storeload屏障 ***
            OrderAccess::storeload();
          } else {
            // 非volatile变量的赋值逻辑
          }       
  }

我们看到这里判断完的的storeload

然后我们介绍一下这个fence函数

这里先判断使用的显卡还是其他显卡

其实没有什么区别,主要是amd使用rsp,其他显卡使用的是esp,使用的寄存器不同

inline void OrderAccess::fence() {
   // 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
  compiler_barrier();
}

这个__asm__就是表示告诉编译器在这里插入汇编代码

volatile就是告诉编译器我这里插入的汇编代码原原本本的给我执行,不许重排序

我们发现这些名字前面都有一个lock就是会将这块内存区域的缓存锁定并写回到主内存中

相关文章
|
4天前
|
Java
JVM之本地内存以及元空间,直接内存的详细解析
JVM之本地内存以及元空间,直接内存的详细解析
40 0
|
4天前
|
存储 Java C++
C++ 引用和指针:内存地址、创建方法及应用解析
C++中的引用是现有变量的别名,创建时需用`&`运算符,如`string &meal = food;`。指针存储变量的内存地址,使用`*`创建,如`string* ptr = &food;`。引用必须初始化且不可为空,而指针可初始化为空。引用在函数参数传递和提高效率时有用,指针适用于动态内存分配和复杂数据结构操作。选择使用取决于具体需求。
41 9
|
1天前
|
存储 Java 程序员
【Python 的内存管理机制专栏】深入解析 Python 的内存管理机制:从变量到垃圾回收
【5月更文挑战第18天】Python内存管理关乎程序性能与稳定性,包括变量存储和垃圾回收。变量存储时,如`x = 10`,`x`指向内存中值的引用。垃圾回收通过引用计数自动回收无引用对象,防止内存泄漏。了解此机制可优化内存使用,避免循环引用等问题,提升程序效率和稳定性。深入学习内存管理对成为优秀Python程序员至关重要。
【Python 的内存管理机制专栏】深入解析 Python 的内存管理机制:从变量到垃圾回收
|
4天前
|
机器学习/深度学习 算法 Linux
xenomai内核解析--实时内存管理--xnheap
Xenomai是一个实时操作系统(RTOS)层,用于Linux,旨在提供确定性的任务调度和服务。其内存管理机制包括一个名为xnheap的内存池,确保内存分配和释放的时间确定性,以满足硬实时系统的严格需求。
28 0
xenomai内核解析--实时内存管理--xnheap
|
4天前
|
算法 安全 Linux
深度解析:Linux内核内存管理机制
【4月更文挑战第30天】 在操作系统领域,内存管理是核心功能之一,尤其对于多任务操作系统来说更是如此。本文将深入探讨Linux操作系统的内核内存管理机制,包括物理内存的分配与回收、虚拟内存的映射以及页面替换算法等关键技术。通过对这些技术的详细剖析,我们不仅能够理解操作系统如何高效地利用有限的硬件资源,还能领会到系统设计中的性能与复杂度之间的权衡。
|
4天前
|
存储 编译器 C语言
C语言基础知识:数据在内存中的存储解析(整数,浮点数)
C语言基础知识:数据在内存中的存储解析(整数,浮点数)
|
4天前
|
Java
Java中的线程同步:synchronized关键字的深度解析
【4月更文挑战第14天】在多线程环境下,线程同步是一个重要的话题。Java提供了多种机制来实现线程同步,其中最常用且最重要的就是synchronized关键字。本文将深入探讨synchronized关键字的工作原理,使用方法以及注意事项,帮助读者更好地理解和使用这一重要的线程同步工具。
|
4天前
|
存储 算法 安全
深度解析JVM世界:JVM内存分配
深度解析JVM世界:JVM内存分配
|
4天前
|
存储
浮点数在内存中的存储
浮点数在内存中的存储
25 0
|
4天前
|
存储
数据在内存中的存储之整数存储
数据在内存中的存储之整数存储
21 0

推荐镜像

更多