4.Java内存模型(JMM)-概述

缓存一致性问题

       计算机在运行程序时,每条指令都是在CPU中执行的,在执行过程中势必会涉及到数据的读写。但数据是存储在主存中,读写主存中的数据没有CPU中执行指令的速度快;

       所以,如果任何的交互都需要与主存打交道则会大大影响效率,那就有了CPU高速缓存。每个CPU都有高速缓存,它只与在该CPU上运行的线程有关;

       在程序运行中,会将运行所需要的数据复制一份到CPU高速缓存中,在进行运算时CPU不再也主存打交道,而是直接从高速缓存中读写数据,只有当运行结束后才会将数据刷新到主存中;

       这会带来一个新的问题:缓存一致性

       举个例子:

i++

      当线程运行这段代码时,首先会从主存中读取i( i = 1),然后复制一份到CPU高速缓存中,然后CPU执行 + 1的操作 (值变成2),然后将i(值为2)写入到高速缓存中,最后刷新到主存中。

      这样做,在单线程中是没有问题的;但在多线程中,会出问题。如下:

      假如有两个线程A、B都执行这个操作(i++),按照我们正常的逻辑,主存中的i值应该=3,但事实是这样么?分析如下:

      两个线程从主存中读取i的值(1)到各自的高速缓存中,然后线程A执行+1操作并将结果写入高速缓存中,最后写入主存中,此时主存i==2;线程B做同样的操作,主存中的i仍然是2。所以最终结果为2并不是3;

      这种现象就是缓存一致性问题;

      解决缓存一致性方案有两种:1.通过在总线加锁的方式;2.通过缓存一致性协议;

      但是方案1存在一个问题,它是采用一种独占的方式来实现的,即总线加锁的话,只能有一个CPU能够运行,其他CPU都得阻塞,效率较为低下;

      方案2,缓存一致性协议(MESI协议)它确保每个缓存中使用的共享变量的副本是一致的。其核心思想如下:当某个CPU在写数据时,如果发现操作的变量是共享变量,则会通知其他CPU告知该变量的缓存行是无效的,因此其他CPU在读取该变量时,发现其无效会重新从主存中加载数据;


Java内存模型

       内存模型可以理解为,在特定的操作协议下,对特定的内存或者高速缓存进行读写访问的过程抽象;

       并发的复杂在于要保证数据的一致性和安全性。因此,我们需要在深入了解并行机制的前提下,再定义一种规则,保证多个线程间可以有效地,正确地协同工作,而JMM也是为此而生的。

       Java内存模型规定所有的变量都存储在主内存中,每条线程还有自己的工作内存(可以与前面讲的处理器的高速缓存类比),线程的工作内存中保存了该线程使用到的变量(主内存副本拷贝);

       线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成;


       而JMM的关键技术点都是围绕着多线程的原子性,可见性和有序性来建立的。

原子性(Atomicity)

       原子性是指,一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行;   

i = 0;             ---1
j = i ;            ---2
i++;               ---3
i = j + 1;         ---4

      上面四个操作,只有1才是原子性的,其他的都不是;

     1—在Java中,对基本数据类型的变量和赋值操作都是原子性操作; 
     2—包含了两个操作:读取i,将i值赋值给j ;
     3—包含了三个操作:读取i值、i + 1 、将+1结果赋值给i; 

     4—同三一样;

     Java只保证了基本数据类型的变量和赋值操作才是原子性的(注:在32位的JDK环境下,对64位数据的读取不是原子性操作,如long、double)。要想在多线程环境下保证原子性,则可以通过锁、synchronized来确保;

     需要注意的是,volatile是无法保证复合操作的原子性的;换句话说,加不加volatile都不会对原子性有什么影响;

可见性(Visibility)

      可见性是指当一个线程修改了某个共享变量的值,其他线程是否能够立即知道这个修改;

      在上面已经分析了,在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的;Java提供了volatile来保证可见性。

      当一个变量被volatile修饰后,表示线程本地缓存无效,当一个线程修改共享变量后会立即更新到主内存中,当其他线程读取共享变量时,它会直接从主内存中读取; 

      当然,synchronize和锁都可以保证可见性。

有序性(Ordering)

      程序执行的顺序按照代码的先后顺序执行。

      但在Java内存模型中,为了效率是允许编译器和处理器对指令进行重排序,重排之后的指令顺序于原来的未必一致;当然重排序它不会影响单线程的运行结果,但是对多线程会有影响。

      Java提供volatile来保证一定的有序性。

      最著名的例子就是单例模式里面的DCL(双重检查锁),可以参考这个

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值