Java中保证多线程间的数据共享的方法详解

 更新时间:2023年11月14日 11:54:23   作者:奔跑的毛球  
这篇文章详解的发给大家介绍了Java中是如何保证多线程间的数据共享的,文中通过图文介绍的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下

在讨论这个问题之前,我们可以先瞅瞅Java的内存模型JMMJMM可不要和JVM混为一谈。我们说的是内存模型JMM(Java Memory Model)

Java的内存模型

稍微解释一下CPU的缓存,这里CPU的缓存有三级,L1,L2和L3。

  • L1是访问速度最快的,是线程独享。
  • L2次之,属于内核独享。
  • L3是最慢的,是多核共享。

当CPU执行指令需要数据的时候,会先在L1,L2,L3中依次寻找,若是找不到,则会去JVM中寻找,而JMM则在CPU和主内存之间来保证我们需要的可见性和有序性。

这个JMM就是Java内存模型的核心,可见性有序性都是在这里实现。
而主内存就是JVM,就是我们的堆内存。

保证可见性的方式

可见性是指,当多个线程操作同一个数据的时候,保证一个线程的修改对其他线程是可见的,也就是说不管多个线程如何操作,如何并发,他们在同一时间取到的值是相同的。

为了保证可见性,我们一般有以下几种方案:

volatile

可以用volatile来修饰基本数据类型,可以保证每次CPU操作数据的时候,都直接操作的是主内存的值。

文末我们会说说volatile的原理

synchronized

对于synchronized来说,是谁拿到锁,谁执行操作,对于拿到锁的线程来说,前边线程的操作是可见的。

我会另起一篇文章专门讲解synchronized。

lock

lock是基于CASvolatile的修改操作,可以保证操作数据时前边操作的可见性。

final

final修饰的是常量啊,没法写,只读,当然是全局可见的

这里有一个小点:

我们清楚,volatile修饰基本数据类型的时候是可以保证可见性的,但是若是修饰的是引用数据类型呢?
一般没人这么搞,甚至平时工作中volatile都很少使用,一不留神系统的性能会降低几个维度。但是面试中常被问到,我们可以这样回答:若volatile修饰的是引用数据类型,则只能保证引用数据类型的地址是可见的,里边的值不可见。就这。

volatile的底层实现

稍微看看volatile的底层实现吧,其实

volatile的底层是汇编的lock指令,这个指令会强行要求将值写入主内存,并且忽略Store Buffer这种缓存,从而达到可见性的目的,同时利用MESI协议,让其他缓存行失效。

我们晓得将java文件编译为class文件的时候,会基于JIT做优化,调整指令的顺序,从而提升执行效率,这个过程叫指令重排
在CPU层面也会调整指令的顺序来提升性能。而这个指令重排会导致一些问题,我们看看volatile是如何解决这个问题的。

原理

被volatile修饰的属性,在编译时会在先后增加内存屏障。这里的提到的内存屏障一般有四种

  • SS: StoreStore屏障前的读写操作必须全部完成,才会继续屏障之后的操作
  • SL: StoreLoad屏障前的写操作必须全部完成,才会继续屏障之后的读操作
  • LL: LoadLoad屏障前的读操作必须全部完成,才能继续屏障之后的读操作
  • LS: LoadStore屏障前的读操作必须全部完成,才能继续屏障之后的写操作

这里volatile的原理就如下

可以看到对于volatile的写操作

之前添加了StoreStore内存屏障,必须完成之前的读写操作才能继续volatile的写操作。
而后添加了StoreLoad屏障,要求其前边的volatile写操作完成,才能继续之后的读操作。

这样就保证了在对volatile修饰的值执行写操作的时候,之前的读写操作已经全部完成,而其后的读操作在等待写操作完成再去读值。

而对于volatile的读操作

在其后添加了一个LoadLoad屏障LoadStore屏障,这里这个LoadLoad屏障不是很理解,查阅了诸多资料也没有眉目,若是有小伙伴知晓的,烦请不吝赐教。
LoadStore屏障则保证了volatile的读操作全部完成之后,再继续之后的写操作。

这样volatile的读操作完成之后,才会执行其他的写操作。保证了读到的值是确定的不变的。

以上就是Java中保证多线程间的数据共享的方法详解的详细内容,更多关于Java多线程间的数据共享的资料请关注脚本之家其它相关文章!

相关文章

  • springboot项目编写发送异常日志到企微工具包的操作方法

    springboot项目编写发送异常日志到企微工具包的操作方法

    本文介绍了Springboot项目如何编写发送异常日志到企业微信的工具包,内容包括创建基础Bean、配置类、pom依赖等步骤,并展示了如何通过nacos进行配置,这为开发者提供了一种有效的日志管理方案,方便快速定位和处理项目中的异常问题,感兴趣的朋友跟随小编一起看看吧
    2024-09-09
  • 利用Java判断一个字符串是否包含某个字符

    利用Java判断一个字符串是否包含某个字符

    在Java编程中,字符串操作是日常开发的常见任务,涉及判断、查找、替换等多种操作,文章介绍了如何在Java中判断字符串是否包含某字符,提供了使用contains方法和字符数组遍历两种基础方法,需要的朋友可以参考下
    2024-11-11
  • SpringBoot配置及使用Schedule过程解析

    SpringBoot配置及使用Schedule过程解析

    这篇文章主要介绍了SpringBoot配置及使用Schedule过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • nacos一直频繁的打印日志get changegroupkeys问题

    nacos一直频繁的打印日志get changegroupkeys问题

    这篇文章主要介绍了nacos一直频繁的打印日志get changegroupkeys问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • 基于JPA实体类监听器@EntityListeners注解的使用实例

    基于JPA实体类监听器@EntityListeners注解的使用实例

    这篇文章主要介绍了JPA实体类监听器@EntityListeners注解的使用实例,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • maven关于pom文件中的relativePath标签使用

    maven关于pom文件中的relativePath标签使用

    在Maven项目中,子工程通过<relativePath>标签指定父工程的pom.xml位置,以确保正确继承父工程的配置,这个标签可以配置为默认值、空值或自定义值,默认情况下,Maven会向上一级目录寻找父pom;若配置为空值
    2024-09-09
  • Spring利用注解整合Mybatis的方法详解

    Spring利用注解整合Mybatis的方法详解

    这篇文章主要为大家介绍了Spring如何利用注解整合MyBatis,文中的示例代码讲解详细,对我们学习有一定的参考价值,需要的小伙伴可以参考一下
    2022-06-06
  • Java编程实现beta分布的采样或抽样实例代码

    Java编程实现beta分布的采样或抽样实例代码

    这篇文章主要介绍了Java编程实现beta分布的采样或抽样实例,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • SpringBoot MyBatis简单快速入门例子

    SpringBoot MyBatis简单快速入门例子

    MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。这篇文章主要介绍了SpringBoot MyBatis快速入门-简单例子,需要的朋友可以参考下
    2021-07-07
  • SpringBoot+MybatisPlus+Mysql+Sharding-JDBC分库分表

    SpringBoot+MybatisPlus+Mysql+Sharding-JDBC分库分表

    本文主要介绍了SpringBoot+MybatisPlus+Mysql+Sharding-JDBC分库分表,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03

最新评论