一篇文章告诉你什么是Java内存模型

这篇具有很好参考价值的文章主要介绍了一篇文章告诉你什么是Java内存模型。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在上篇 并发编程Bug起源:可见性、有序性和原子性问题,介绍了操作系统为了提示运行速度,做了各种优化,同时也带来数据的并发问题,

定义

在单线程系统中,代码按照顺序从上往下顺序执行,执行不会出现问题。比如一下代码:

int a = 1;
int b = 2;
int c = a + b;

程序从上往下执行,最终c的结果一定会是3

但是在多线程环境中,代码就不一定会顺序执行了。代码的运行结果也有不确定性。在开发中,自己本地没问题,一行行查看代码也没有问题,但是在高并发的生产环境就会出现违背常理的问题。

多线程系统提升性能有如下几个优化:

  • 单核的cpu改成多核的cpu,每个cpu都有自己的缓存。
  • 多个线程可以在cpu线程切换。
  • 代码可能根据编译优化,更新代码的位置。

这些优化会导致可见性原子性以及有序性问题,为了解决上述问题,Java内存模型应运而生。

Java内存模型是定义了Java程序在多线程环境中,访问共享内存和内存同步的规范,规定了线程之间的交互方式,以及线程与主内存、工作内存的的数据交换。

Java内存模型解决并发

导致可见性的原因的是缓存,导致有序性的问题是编译优化,那解决可见性、有序性问题就是禁用缓存和编译优化。这样虽然解决了并发问题,但是性能却下降了。

合理的方案就是按需求禁用缓存和编译优化,在需要的地方添加对应的编码即可。Java内存模型规范了JVM如何按需禁用缓存和编译优化,具体包括volatilesynchronizedfinal这几个关键字,以及Happens-Before规则。

可见性问题

在多核cpu操作系统中每次cpu都有自己的缓存,cpu先从内存获取数据,再进行运算。比如下图中线程A和线程B,分别运行自己的cpu,然后从内存获取变量到自己的cpu缓存中,并进行计算。

线程B改变了变量之后,线程A是无法获取到最新的值。以下代码中,启动两个线程,线程启动完线程A,循环获取变量,如果是true,一直执行循环,直到被改成false才跳出循环,然后再延迟1s启动线程B,线程修改变量值为true:

private static boolean flag = true;

// 线程A一直读取变量flag,直到变量为false,才跳出循环
class ThreadA extends Thread {
    @Override
    public void run() {
        while (flag) {
            // flag 为 true,一直读取flag字段,flag 为 false 时跳出来。
            //System.out.println("一直在读------" + flag);
        }
        System.out.println("thread - 1 跳出来了");
    }
}
// 1s 后线程B将变量改成 false
class ThreadB extends Thread {

    @Override
    public void run() {
        System.out.println("thread-2 run");
        flag = false;
        System.out.println("flag 改成 false");
    }
}

@Test
public void test2() throws InterruptedException {
    new Thread1().start();
    // 暂停一秒,保证线程1 启动并运行
    Thread.sleep(1000);
    new Thread2().start();
}

运行结果:

thread-2 run
flag 改成 false

线程A一直处于运行中,说明线程B修改后的变量,线程A并未知道。

flag变量添加volatile声明,修改成:

private static volatile boolean  flag = true;

再运行程序,运行结果:

thread-2 run
flag 改成 false
thread - 1 跳出来了

线程B运行完后,线程A也跳出了循环。说明修改了变量后,其他线程也能获取最新的值。

一个未声明volatile的变量,都是从各自的cpu缓存获取数据,线程更新数据之后,其他线程无法获取最新的值。而使用volatile声明的变量,表明禁用缓存,更新数据直接更新到内存中,每次获取数据都是直接内存获取最新的数据。线程之间的数据都是相互可见的。

可见性来自happens-before规则,happens-before用来描述两个操作的内存可见性,如操作Ahappens-before操作B,那么A的结果对于B是可见的,前面的一个操作结果对后续操作是可见的happens-before定义了以下几个规则:

  • 解锁操作happens-before同一把锁的加锁操作。
  • volatile 字段的写操作happens-before同一字段的读操作。
  • 线程的启动操作happens-before该线程的第一个操作。
  • Ahappens-beforeB,且Bhappens-beforeC,那么Ahappens-beforeC。happens-before具有传递性。

有序性问题

先看一个反常识的例子:

int a=0, b=0;
public void method1() {
    b = 1;
    int r2 = a; 
}

public void method2() {
    a = 2; 
    int r1 = b; 
}

定义了两个共享变量ab,以及两个方法。第一个方法将共享变量b赋值为1 ,然后将局部变量r2赋值为a。第二个方法将共享变量a赋值为2,然后将局部变量r1赋值为b

在单线程环境下,我们可以先调用第一个方法method1,再调用method2方法,最终得到r1r2的值分别为1,0。也可以先调用method2,最后得到r1r2的值分别为0,2

如果代码没有依赖关系,JVM编译优化可以对他们随意的重排序,比如method1方法没有依赖关系,进行重排序:

int a=0, b=0;
public void method1() {
    int r2 = a; 
    b = 1;
}

public void method2() { 
    int r1 = b; 
    a = 2;
}

此时在多线程环境下,两个线程交替运行method1method2方法:

重排序后r1r2分别是0,0

那如何解决重排序的问题呢?答案就是将变量声明为volatile,比如a或者b变量声明volatile。比如b声明为volatile,此时b的赋值操作要happens-before r1的赋值操作。

int a=0;
volatile int b=0;
public void method1() {
    int r2 = a; 
    b = 1;
}

public void method2() { 
    int r1 = b; 
    a = 2;
}

同一个线程顺序也满足happens-before关系以及传递性,可以得到r2的赋值happens-before a的赋值。也就表明对a赋值时,r2已经完成赋值了。也就不可能出现r1r200的结果。

内存模型的底层实现

Java内存模型是通过内存屏障来实现禁用缓存和和禁用重排序

内存屏障会禁用缓存,在内存写操作时,强制刷新写缓存,将数据同步到内存中,数据的读取直从内存中读取。

内存屏障会限制重排序操作,当一个变量声明volatile,它就插入了一个内存屏障,volatile字段之前的代码只能在之前进行重排序,它之后的代码只能在之后进行重排序。

总结

Java内存模型(Java Memory Model,JMM)定义了Java程序中多线程之间共享变量的访问规则,以及线程之间的交互行为。它规定了线程如何与主内存和工作内存交互,以确保多线程程序的可见性、有序性和一致性。

  • 可见性:使用volatile声明变量,数据读取直接从内存中读取,更新也是强制刷新缓存,并同步到主内存中。

  • 有序性:使用volatile声明变量,确保编译优化不会重排序该字段。

  • Happens-Before: 前面一个操作的结果对后续操作是可见的

参考

  • Java内存模型

  • Java内存模型:看Java如何解决可见性和有序性问题文章来源地址https://www.toymoban.com/news/detail-455777.html

到了这里,关于一篇文章告诉你什么是Java内存模型的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 这篇文章告诉你ai绘画软件有哪些

    欢迎来到AI绘画教程!随着人工智能技术的不断发展,越来越多的艺术家和设计师开始探索如何将AI应用于他们的作品中。AI绘画是一种令人兴奋的新领域,它可以让我们以前从未想过的方式来创作艺术。 本文,我们将分享几个ai绘画教程。即使你没有任何绘画经验,也不用担

    2024年02月09日
    浏览(50)
  • 光伏销售业务好做吗?这篇文章告诉你

    随着人们对环保和清洁能源的需求不断增加,光伏行业也在迅速发展。光伏产品的销售前景非常好,但想要做光伏销售业务必须要具备一定的技术和服务能力。 一、了解市场,掌握趋势 光伏业务的市场状况和发展趋势是每个业务员必须了解的内容。通过研究政策走向、市场

    2024年01月18日
    浏览(275)
  • 如何真正认识 Linux 系统结构?这篇文章告诉你

         Linux 内核 内核是操作系统的核心,具有很多最基本功能,它负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。 Linux 内核由如下几部分组成:内存管理、进程管理、设备驱动程序、文件系统和网络管理等。如图:   系统调用接

    2024年02月01日
    浏览(49)
  • ai写作生成软件哪些好用?这篇文章会告诉你

    快要放暑假了,我那个读小学的弟弟说,暑假作业肯定又要写日记了,每天一篇,实在没什么好写的。我虽然一开始还劝说他几句,没事也得找点事做,这样才有素材写日记。但还是拗不过他的撒娇,确实我自己当年写日记大多也是胡编乱造的。不过现在不一样了,有了ai写

    2024年02月16日
    浏览(39)
  • 到底什么是机器学习模型?这篇文章终于讲明白了

    机器学习 (Machine Learning) 是对研究问题进行模型假设,利用计算机从训练数据中学习得到模型参数,并最终对数据进行预测和分析的一门学科。 **模型是机器学习的核心组成要素。**本文从模型的广义概念出发,引申出机器学习模型的基本定义,并就机器学习中容易混淆的概念

    2024年02月05日
    浏览(45)
  • 我试图通过这篇文章告诉你,这行源码有多牛逼。

    你好呀,我是歪歪。 这次给你盘一个特别有意思的源码,正如我标题说的那样:看懂这行源码之后,我不禁鼓起掌来,直呼祖师爷牛逼。 这行源码是这样的: java.util.concurrent.LinkedBlockingQueue#dequeue h.next = h,不过是一个把下一个节点指向自己的动作而已。 这行代码后面的注释

    2024年02月04日
    浏览(39)
  • 这篇文章来告诉你几个实用的视频转文字的方法

    相信大家在闲暇之余,都会通过一些网课来提高自己的知识本领吧!有的时候在上网课的过程中,会感觉自己做笔记的速度赶不上老师的进度,重复观看又比较麻烦,这时我们就可以借助一些视频转换软件来将视频转换成文字,这样就可以让我们清楚的知道视频的内容了。那

    2023年04月27日
    浏览(40)
  • 不知道如何裁剪视频画面尺寸?这篇文章告诉你如何视频裁剪画面

    现在短视频行业盛行,相信应该有不少小伙伴想加入短视频的制作,当我们编辑完视频想上传到短视频平台上,会发现有些平台会限制视频画面的大小,导致视频发布不了,其实我们可以在发布前将视频裁剪至合适大小,那应该如何裁剪视频画面尺寸呢?可以试试看下面这三

    2023年04月14日
    浏览(39)
  • 软件测试/人工智能/全日制|GitHub怎么用,这篇文章告诉你

    前言 作为一个刚刚接触代码的程序员,可能我们会听到一个词 GitHub ,把代码提交到 GitHub 上,或者从 GitHub 上克隆项目到本地,在 GitHub 上查看某个工具的文档等等,我们不禁要问, GitHub 究竟是什么,该怎么用,本文就给各位初学者们介绍什么是 GitHub ,它能帮我们干什么?

    2024年02月02日
    浏览(57)
  • 计算机系大学生,可以通过Java做什么副业?这篇文章给你答案!

    计算机系科班出身的学生,有什么能做的副业?最佳答案就是做自己专业对口,且促进技能的事,**敲代码做课设!**这篇文章就来分享下自己的副业之旅(仅限计算机相关专业哦) 发现副业机会 在我大三时无意将Javaweb作业分享出去,收到了不少回复,很多人问我可以定做课

    2024年02月06日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包