• Java内存模型


    并发编程模型

      并发编程中需要处理的两个关键性的问题是:线程之间的通信以及线程之间的同步。在命令式编程中,有两种通信方式:共享内存和消息传递
    • 共享内存:读写内存中公共状态来隐式实现线程之间的通信,共享内存通信的同步机制是显示进行的,程序开发人员需要在某个代码或者某个方法显示的进行互斥执行
    • 消息传递:通过明确的发送消息来实现通信,线程之间没有公共状态,消息传递通信的同步机制是隐式的,消息的发送在接收之前
    Java的通信机制是共享内存
     
    内存模型
      在计算机中,数据的读写涉及到CPU和内存,由于CPU的处理速度非常快,而从内存中进行读写的速度慢,这样导致指令执行速度降低,所以CPU里面引入了高速缓存。数据的读写的过程就是先从内存中读取数据到高速缓存中,然后在高速缓存中计算,最后把最终结果写回至内存中。这种处理机制在单线程中是没有问题的,但是在多CPU,每条线程可能运行在不同的CPU中,这样就是导致一个问题,一个变量在不同的CPU都存在缓存,这样都存在数据的一致性问题。
     
    如何解决这个问题?
    1. 在总线上加LOCK锁
    2. 通过缓存一致性协议
    在早期的CPU当中,是通过在总线上加LOCK#锁的形式来解决缓存不一致的问题。因为CPU和其他部件进行通信都是通过总线来进行的,如果对总线加LOCK#锁的话,也就是说阻塞了其他CPU对其他部件访问(如内存),从而使得只能有一个CPU能使用这个变量的内存。这样处理效率比较低下。所以就出现了缓存一致性协议。最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

     

    Java内存模型(JMM)

      JMM 定义个一个线程的写入何时对另外线程可见,可以理解为JMM是对计算机内存模型的一个抽象。在JMM中,共享变量存储在主内存中(计算机存储在内存中),每个线程拥有自己独立的本地内存(计算机每个线程有自己的高速缓存),JMM中线程的读写是先从主内存读取值到自己本地内存,在本地内存中进行读写,最后将最终结果刷新到主内存中。

    内存间的交互操作

    在JMM模型定义了八种操作
    • lock(锁定):作用于主内存变量,把一个变量标识为一个线程独占的状态
    • unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量可以被其他线程锁定
    • read(读取):作用于主内存变量,把一个变量的值从主内存中传输到线程的工作内存中,以便随后的load操作使用
    • load(加载):作用于工作内存变量,把read操作从主内存的读取的变量的值放入到工作内存的变量副本中
    • use(使用):作用于工作内存变量,把工作内存变量的值传递给执行引擎,每当虚拟机需要使用这个变量的字节码的指令时都会执行这个操作
    • assign(赋值):作用于工作内存变量,把一个从执行引擎得到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
    • store(存储):作用于工作内存变量,将工作内存的变量的值传送到主内存中,以便后续的write操作使用
    • write(写入):作用于主内存变量,把store操作从工作内存得到的值写入到主内存变量中
    从主内存复制到工作内存,必须顺序执行 read 和 load 操作,从工作内存复制到主内存,必须顺序执行 store 和 write 操作
    JMM规定执行以上八个操作的规则:
    • 不允许read和load、store和write 单一出现,也就是说需要成双成对对线
    • 不允许一个线程丢弃它最近的assign操作,就是说工作内存变量发生改变,必须将改变同步至主内存中
    • 不允许线程无原因(没有发生assign操作)把工作内存变量同步至主内存中
    • 对一个变量执行store和write操作执行,必须执行过了assign和load操作
    • 一个变量只能有一个线程执行lock操作,一个线程可以执行多个lock,但是对应的需要执行同样的unlock操作
    • 对一个变量执行lock操作,会清空工作内存此变量的值,在执行引擎使用该变量的前,需要执行load和assign操作
    • 在执行unlock之前,必须将变量的改变同步到主内存中,就是执行store和write操作
    • lock和unlock需要成双成对出现

    原子性/可见性/有序性

      Java内存模型是按照原子性、可见性、有序性三个特征来建立的。并发需要正确的执行,需要保证原子性 可见性 有序性,有一个没保证有可能会导致运行不正确。

    • 原子性:一个操作或者多个操作要么全部执行而且执行过程不被打断,要么全部不执行。
    • 可见性:多个线程访问一个变量,一个线程修改了共享变量,其他线程能立刻看到这个变量的修改
    • 有序性:程序执行的顺序按照代码的先后顺序执行

      在JMM中如何保证原子性 可见性 有序性

    1. 在Java内存模型中,只保证基本数据类型的读写是原子性操作,如果需要保证多范围的原型性,需要通过同步控制,可以使用 synchronizedlock来实现,保证同一时刻只有一个线程执行该代码块。
    2. 对于可见性,Java中提供了volatitle保证可见性。 通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
    3. 在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。 在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
    4. 在JMM中,有一些先天的有序性,不需要任何手段保证有序性,称为happen-before原则

    happen-before原则

    1. 程序的次序原则:一个线程内,按照代码的次序,每一个操作happen-before于后续动作
    2. 锁规则:解锁unlock happen-before于加锁 lock
    3. volatile原则:对一个volatile变量的写 happen-before于对volatile变量的读
    4. 传递性:A happen-before B,B happen-before C => A happen-before C
    5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
    6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
    7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
    8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
  • 相关阅读:
    第05组 Alpha冲刺 (6/6)
    第05组 Alpha冲刺 (5/6)
    第五次作业
    第05组 Alpha冲刺 (4/6)
    第05组 Alpha冲刺 (3/6)
    第05组 Alpha冲刺 (2/6)
    第05组 Alpha冲刺 (1/6)
    第四次作业
    差分约束
    置换群的性质与burnside引理
  • 原文地址:https://www.cnblogs.com/xiaojianfeng/p/9401387.html
Copyright © 2020-2023  润新知