java多线程(三)—— java内存模型JMM

4 篇文章 0 订阅
4 篇文章 1 订阅

java多线程(三)—— java内存模型JMM

  此篇文章准备研究的就是java中的JMM,也就是java内存模型(Java Memory Model),那到底为什么要有JMM?什么又是JMM呢?它又有什么作用?它和java内存结构又有什么样的区别?

1、cpu缓存

  要知道为什么有JMM,JMM是什么?我们得要从cpu缓存模型说起,当我们介绍完cpu缓存模型,并且知道了cpu缓存模型中所会出现的问题以后,对于JMM就会有很直观的认识,那cpu缓存模型长啥样呢?请由我慢慢道来。

1.1、cpu缓存模型

  首先我们需要知道cpu使用缓存的目的。我们知道,cpu在对程序中的数据进行处理时,需要和内存进行数据的交换,而随着技术的发展,cpu的执行速度越来越快,而内存的读写速度并没有很明显的提升,这就使得cpu的读写速度限制了cpu的执行速度,当然,我们肯定不能让这种事情发生,所以就采取了cpu缓存的机制来解决内存读写速度匹配不到cpu执行速度的问题,通过在内存与cpu的数据交换中间加上一个缓存,通过这个缓存来解决速度不匹配的问题,当cpu需要进行数据交互时,先去缓存中查看是否有需要的数据,有就直接读写,没有就去内存中获取,在缓存中进行更改后的数据最终也要同步到内存中。该缓存的读写速度比内存的速度大的多,价格也贵得多,所以缓存的大小比内存晓得多,缓存一般是由寄存器或者高速缓存充当。
在这里插入图片描述
在这里插入图片描述

  上两张图所示就是我们的cpu缓存模型,就是一个三级缓存的结构,从一级缓存到三级缓存的容量逐渐增大,而读写速度一次减小,一级缓存中的数据是二级缓存中的一部分,二级缓存中的数据是三级缓存中的一部分,三级缓存中的数据又是内存中的一部分,cpu会从上到下去获取数据,上一级缓存中没有就会到下一级缓存中获取,而当对某一级缓存的数据做出更改后就会同步到下一级缓存中,直至同步到内存中。我们从第二张图可以知道,L1也就是一级缓存是由多个寄存器组成,L2、L3是统一的高速缓存,cpu是直接和L1一级缓存中的寄存器进行交互的,这就是cpu的缓存模型。

1.2、cpu缓存导致的问题

  cpu缓存模型在多核cpu、多线程的情况下会出现缓存不一致,值覆盖的问题,我们来分析分析产生这些问题的原因。
  缓存不一致:

  1. 假如现在两个线程在并行运行,且共同操作内存中的变量i,刚开始两个内核中缓存中的i值都是5。

在这里插入图片描述

  1. 然后内核1中的线程将i更改为10,且同步到下一级缓存和内存,此时内核1中的缓存和内存中的i值都是10,而内核2线程
    中对i的操作值都是为5,这就造成了缓存不一致的问题。

在这里插入图片描述
  上面的步骤就是因为当内核1中对i进行更改,同步到内存中后,而内核2中的缓存中存储的还是旧值,也就是内核1中对于共享变量的改变,内核2中是不可见的,也就是它不满足可见性。可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
  覆盖问题:

  1. 初始i=5,线程1要做i++,线程2也要做i++,线程1运行在内核1中,线程2运行在内核2中,且两个内核中的缓存中的i都是5。

在这里插入图片描述

  1. 线程1做i++操作,使得i=6,并最终同步到内存中。

在这里插入图片描述

  1. 线程2也做i++操作,使得i=6,并最终同步到内存中。最终i的值被线程2中的值所覆盖,所以产生了覆盖问题。

在这里插入图片描述
  通过上面的步骤我们得到最终的结果i=6,很显然是不对的,i++了两次,最终的正确结果应该为7,发生这种情况的原因就是在多核cpu下,我们的cpu将共享资源的旧值先保存在了缓存当中,然后我们在旧值基础上进行改变,最终同步到内存中,而在这过程中,有可能内存中的旧值已经发生了改变,这就使得最终同步到内存中的值出现了差错。也就是它不满足原子性,原子性就是指cpu在对资源的一系列指令操作中,要么完全执行完,要么就一条都不执行。上面的步骤如果满足原子性,它的步骤是这样的,先让线程1得到i的值,做完i++,最终同步到内存中以后,然后线程2再开始获得i的值,再做完i++,最终再同步到内存中。

  指令重排:
  指令重排是指处理器和编译器在不影响代码在单线程情况下执行的结果,对源代码的指令进行重新排序执行。这种重新排序是一种优化策略,目的是使处理器中运算单元能够充分被利用,从而达到提升程序的整体运行效率。
  指令重排在单线程可运行过程中不影响结果,但是如果在多个线程的运行过程中,因为做了指令的重排,有可能就会使得最终的运行结果发生很大的错误,这就会导致有序性问题。有序性即程序执行的顺序按照代码的先后顺序执行。

2、java内存模型

  通过上面的介绍,我们知道了在cpu的缓存模型中会使得程序出现可见性问题和原子性问题,并且因为指令重排,使得程序发生了有序性问题。注意这三个问题所产生的原因都是因为并发编程,就是在多线程对共享资源操作过程中所引发的,所以,为了满足在并发编程中满足原子性、可见性和有序性,那我们应该采取怎样的机制来解决呢?其实这就可以引入我们今天研究的主题“内存模型”,内存模型就是为了解决多线程对共享内存的操作具有原子性、可见性和有序性问题,而制定的读写操作规范,它通过一些规则来保证并发编程时程序满足原子性、可见性和有序性,这就是内存模型。
  java内存模型就是指在java中,为了解决在并发编程中所出现的原子性、可见性和有序性问题,java这门语言所制定出来的一套对于多线程操作共享资源的读写操作规范。java内存模型规定了所有变量都存储在主内存中,而每一个线程都有一个工作内存,所有工作内存中的数据都是主内存中数据的拷贝,且各自线程所操作的数据也是自己工作内存中的数据,在操作完以后最终写入到主内存中,各自线程都不能操作其他线程工作内存中的数据,只有通过同步到主内存中才有机会进行变量的传递。其实java中的工作内存就是cpu缓存模型中三级缓存的抽象,而主内存就是cpu缓存模型中的内存。所以对于java内存模型,换个角度说明就是JMM规定了java程序如何在工作内存和主内存中如何做数据同步。

在这里插入图片描述

3、JMM的实现

3.1、原子性

  为了保证并发编程中的原子性,在java中通过使用关键字synchronized来保证方法和代码段操作是具有原子性的,具体的使用一级原理在上一篇文章中有论述过,这里就不详细说明了。

3.2、可见性

  java中解决并发编程的可见性的方法有如下三种:

  1. volatile:在变量的前面加上volatile关键字,则该变量在被修改后会立即同步到主内存中,而在每一次使用前都从主内存中重新获取到该值。
  2. synchronized:当一个线程中用该关键字修饰的方法或者代码块对共享资源操作时,强迫该线程的工作内存中共享变量的值从主内存中重新获取。
  3. final:在变量前面加上final关键字表明该变量是不可变的,那就使得对应的变量都是可见的。

3.2、有序性

  1. volatile: 关键字会禁止指令重排序。加上volatile关键字的变量,会使得该变量写操作之前的指令不会重排到写操作的后面,该变量读之后的指令重排到变量读的后面。
  2. synchronized :关键字通过互斥保证同一时刻只允许一条线程操作。这样就相当于单线程工作了,就不会出现有序性问题。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值