关于Java中的可见性和有序性问题

 更新时间:2023年08月07日 08:54:48   作者:吃饭只吃猪脚饭  
这篇文章主要介绍了关于Java中的可见性和有序性问题,Java在诞生之初就支持多线程,自然也有针对这三者的技术方案,今天就学习一下Java如何解决其中的可见性和有序性导致的问题,需要的朋友可以参考下

Java 如何解决可见性和有序性问题

并发场景中,因可见性、原子性、有序性导致问题常常导致bug,Java在诞生之初就支持多线程,自然也有针对这三者的技术方案

今天就学习一下Java如何解决其中的可见性和有序性导致的问题,就引来了今天的主角儿——Java内存模型

什么是Java内存模型

​ 导致可见性的原因是缓存,导致有序性的原因是编译优化

解决可见性、有序性最直接的方法就是禁用缓存和编译优化,但这样会导致性能下降,我们应该想到的是如何按需禁用缓存和编译优化。

​ Java内存模型规范了JVM如何提供按需禁用缓存和编译优化的方法,这些方法包括volatilesynchronizedfinal三个关键字以及六项Happens-Before规则。

使用volatile困惑

volatile关键字在C语言也有,最原始的意义就是禁用CPU缓存。

例如:

volatile int x = 0

表达就是不能使用CPU缓存,必须从内存中读取或者写入。

看起来没啥问题,但是在实际使用却会带了困惑。

例如:

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }
  public void reader() {
    if (v == true) {
      System.out.println(x);
    }
  }
}

假如有A线程调用writer(),B线程调用reader(),输出来的x会是多少呢,在JDK1.5之前,x可能是0或者42,这是因为CPU缓存导致可见性问题,JDK1.5之后Java内存模型对volatile语义进行了增强,因此就引出了Happens-Before规则。

Happens-Before规则

Happens-Before规则就是保证前面的一个操作的结果对后续操作是可见的

它约束了编译器的优化行为,虽允许编译器优化,但是要求编译器优化后一定遵守Happens-Before规则:

规则一:程序的顺序性规则

​ 这条规则是指在一个线程中,按照程序顺序,前面的操作 Happens-Before 于后续的任意操作。

这还是比较容易理解的,比如刚才那段示例代码,按照程序的顺序,第 6 行代码 “x = 42;” Happens-Before 于第 7 行代码 “v = true;”,这就是规则 1 的内容,也比较符合单线程里面的思维:程序前面对某个变量的修改一定是对后续操作可见的。

规则二:volatile 变量规则

​ 这条规则是指对一个 volatile 变量的写操作, Happens-Before 于后续对这个 volatile 变量的读操作,即对后面的读操作可见。

规则三:传递性

​ 这条规则是指如果A Happens-Before B,B Happens-Before C,那么 A Happens-Before C。

规则四:管程中锁的规则

​ 这条规则是指对一个锁的解锁Happens-Before于后续对这个锁的加锁。

管程是一种通用的同步原语,在Java中指的就是synchronized,synchronized是Java里对管程的实现。

tip: 在操作系统中,管程的定义如下: 管程是由一组数据以及定义在这组数据之上的对该组数据操作的操作组成的软件模块,称之为管程。

基本特性:

1. 局部于管程的数据只能被局部于管程内的过程所访问。

2. 一个进程只有通过调用管程内的过程才能进入管程访问共享数据

3. 每次仅允许一个进程在管程中执行某个内部过程。

注意:由于管程是一个语言的成分,所以管程的互斥访问完全由编译程序在编译时自动添加,无需程序员关注

​ 管程中的锁在Java是隐式实现的,加锁以及释放锁都是编译器帮我们实现的。

synchronized (this) { //此处自动加锁
 // x是共享变量,初始值=10
 if (this.x < 12) {
   this.x = 12; 
 }  
} //此处自动解锁

规则五:线程start()规则

​ 这条是关于线程启动的,指主线程A启动子线程B后,子线程B能够看到主线程在启动子线程B前的操作。

规则六:join()规则

​ 这条是关于线程等待的,它是指主线程A等待子线程B完成(主线程A通过调用子线程B的join()方法实现),当子线程B完成后(主线程A中join()方法返回)

主线程能够看到子线程的操作。这里的“看到”,指的是对共享变量的操作。

通过调用子线程B的join()方法实现),当子线程B完成后(主线程A中join()方法返回)

主线程能够看到子线程的操作。这里的“看到”,指的是对共享变量的操作。

到此这篇关于关于Java中的可见性和有序性问题的文章就介绍到这了,更多相关Java可见性和有序性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 通过Java来测试JSON和Protocol Buffer的传输文件大小

    通过Java来测试JSON和Protocol Buffer的传输文件大小

    这篇文章主要介绍了通过Java来测试JSON和Protocol Buffer的传输文件大小,Protocol Buffer(文中简称Protobuffer)是谷歌开发的新的文件传输格式,需要的朋友可以参考下
    2015-12-12
  • java分形绘制科赫雪花曲线(科赫曲线)代码分享

    java分形绘制科赫雪花曲线(科赫曲线)代码分享

    部分与整体以某种形式相似的形,称为分形,科赫曲线是一种外形像雪花的几何曲线,所以又称为雪花曲线,它是分形曲线中的一种,画法如下
    2013-12-12
  • 关于国际化、OGNL表达式语言

    关于国际化、OGNL表达式语言

    本篇文章,小编为大家介绍关于国际化、OGNL表达式语言,有需要的朋友可以参考一下
    2013-04-04
  • Java利用Spire.XLS for Java实现自动化生成PDF文档

    Java利用Spire.XLS for Java实现自动化生成PDF文档

    在 Java 后端高效灵活地实现 PDF 文档的生成,常常是困扰开发者的一个痛点,本文将为您介绍如何利用 Spire.XLS for Java 轻松驾驭 Java 中的 PDF 文档生成,感兴趣的小伙伴可以了解下
    2026-02-02
  • Java中实现线程的超时中断方法实例

    Java中实现线程的超时中断方法实例

    之前在使用Java实现熔断降级组件的时候,需要实现接口请求的超时中断,通过查找相关资料了解了相关的方法,下面这篇文章主要给大家介绍了关于Java中实现线程的超时中断的相关资料,需要的朋友可以参考下
    2018-06-06
  • Mac下卸载IntelliJ IDEA实现过程

    Mac下卸载IntelliJ IDEA实现过程

    本文介绍了Mac系统下彻底卸载IDEA的方法,包括删除应用程序文件、支持文件、设置文件、程序状态文件、偏好设置数据、缓存文件、日志数据、其他相关文件等,最后提供了一键删除命令,但需要注意确认文件目录情况
    2026-05-05
  • LambdaQueryWrapper与QueryWrapper的使用方式

    LambdaQueryWrapper与QueryWrapper的使用方式

    这篇文章主要介绍了LambdaQueryWrapper与QueryWrapper的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • SpringBoot集成XXL-JOB实现任务管理全流程

    SpringBoot集成XXL-JOB实现任务管理全流程

    XXL-JOB 是一款轻量级分布式任务调度平台,功能丰富、界面简洁、易于扩展,本文介绍如何通过 Spring Boot 项目,使用 RestTemplate 和 Feign 的方式调用 XXL-JOB 后台管理接口,实现任务的全生命周期管理,需要的朋友可以参考下
    2025-08-08
  • JAVA截取字符串的几种常用方法

    JAVA截取字符串的几种常用方法

    这篇文章主要给大家介绍了关于JAVA截取字符串的几种常用方法, 在处理字符串的过程中有很多情况下会遇到需要截取字符串的情况,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-09-09
  • 使用IDEA打jar包的详细图文教程

    使用IDEA打jar包的详细图文教程

    JAR文件是一种压缩文件,与常见的ZIP压缩文件兼容,被称为JAR包,下面这篇文章主要给大家介绍了关于使用IDEA打jar包的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-08-08

最新评论