多线程基础(一)— Java 内存模型

  为了更好的理解 Java 内层模型,我们需要简单地将 CPU 缓存模型回忆一下。

CPU Cache 缓存

  在计算机中,虽然 CPU 的计算速度很快,但是计算机中绝大多数的任务不能只靠 CPU 的计算就能完成。还需要包括与内层的数据交互,读写、存储元算结果等。但是由于计算机的存储设备和 CPU 的运算速度有着几个数量级的差距,现代计算机中一般都会在内存设备和 CPU 之间添加一层读写速度尽可能接近 CPU 处理速度的高速缓存,用于将运算需要的数据复制到缓存中,让运算能够快速进行,当运算结束后再从缓存刷新到主内存中,这样 CPU 就无需等待缓慢的内层读写了。

  虽然高速缓存的存储交互很好的解决了处理器与内存的速度矛盾,但它也引入了新的矛盾:内存一致性问题。当多个处理器的运算任务都设及同一块主内存区域时,就会导致内存一致性问题,为了解决这个问题,各个处理器访问缓存的时候都需要遵循一些规则,来保持数据的一致性。这些规则被专家们命名为缓存一致性协议。其中最为出名的是 Intel 的 MESI 协议。MESI 协议保证了每一个缓存中使用的共享变量副本保持一致。大致思想是:当 CPU 在操作缓存中的数据时,如果发现该变量是一个共享变量,也就是说在其他的缓存中也存在该变量的副本,那么就要进行如下操作:

    1)读取操作,不做任何处理,可直接将缓存中的数据读取到 CPU 中的寄存器;

    2)写入操作,发出信号通知其他缓存,该变量的副本已经作废,如果要进行变量读取,必须到主内存中读取新值。

  除了增加高速缓存外,为了使处理器内部的运算单元能够尽量被充分利用,处理器可能会将输入代码进行乱序执行实现优化,同时保证整体代码的计算结果与代码按照逻辑顺序执行的结果是一致的。与 CPU 的乱序执行优化类似,Java 虚拟机的即时编译器也有类似的指令重排序(Instruction Reorder)优化。

Java 内存模型

  Java 内存模型(Java Memory Model, JMM)定义了 Java 虚拟机与计算机主内存进行工作的交互细节。即在 JVM 中将变量存储到内存与从内存中取出变量这样的底层细节。此处的变量包括了实例字段、静态字段和数组对象,也就是可以被共享的数据。不包括局部变量和方法参数,因为他们是线程私有的,不会被共享,也就不会存在竞争问题。JMM 定义了线程和主内存之间的抽象关系,具体如下:

  1)共享变量存储与主内存中,每个线程都可以访问,这里的主内存可以类比CPU 缓存模型中的的主内存,但此处只是虚拟机内存的一部分。

  2)每个线程都有自己的私有内存,称之为本地内存,可以类比 CPU 缓存模型中的高速缓存。

  3)工作内存只能存储该线程的共享变量的副本。

  4)线程对变量的所有操作(读取、写入)都必须在工作内存中进行,不能直接操作主内存中的变量。

  5) 不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

  了解了 Java 内存模型可以为我们以后学习多线程、valatile 关键字打下基础。

  在并发编程中,有三个至关重要的特性,分别是原子性、可见性和有序性。我们来看一下JMM如何保证这三大特性的。

JMM 与 原子性

  原子性的意思是对于以一个操作所包含的所有步骤,要么全部执行,要么全都不执行。这个概念可以类比关系数据库中事物的一致性。

在 Java 中,对基本数据类型和引用类型的变量进行读取、赋值操作是原子性的。但是几个具有原子性的操作组合在一起,就不一定是原子性的了。比如下面这个常见的自增操作:

y++

  这条语句其实由3个操作组合而来:

  1)执行线程从主内存中读取 y 值(如果 y 的副本已经存在于执行线程的工作内存中,则直接获取),然后将其副本存入当前线程的工作内存中;

  2)在执行线程中为 y 执行加一操作;

  3)将 y 的值刷新进主内存。

  JMM 只保证了基本的读取与赋值操作的原子性,其他的均不保证,如果想要某些代码获得原子性,可以使用在后面会学到的 synchronized 关键字,或者 JUC 中的 lock。

JMM 与 可见性

  可见性是指一个程序对一个共享变量进行了修改,其他线程能立即得知这个修改。JMM 通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷入变量新值,这种依赖主内存作为传递媒介的方式来实现可见性的。Java 中提供了很多保证有序性的方式:

  1)使用 volatile 关键字,当一个变量被 volatile 修饰时,对于共享资源的读操作会直接在主内存中进行(当然也会刷新到工作内存),对于共享资源的写操作会首先修改工作内存,然后将值立马刷新到主内存中。

  2)使用 synchronized 关键字,synchronized 关键字保证在同一时间只有一个线程获得锁,然后执行同步方法,对共享变量进行操作。并且会确保在锁释放之前,会将对变量的修改刷新到主内存中。

  3)使用 JUC 提供的显式锁lock,作用和 synchronized 关键字相似,但与synchronized 关键字相比,更加轻量级。

JMM 与 有序性

  在 JMM 中,允许编译器和 CPU 对指令进行重排序,也就是在 CPU 缓存中讲的乱序执行优化和指令重排序优化。在单线程的情况下,随便你们想怎么优化,都没问题,但对于多线程,指令重排可能会引起错误。JMM 天生具备一些有序性规则,不需要任何显示的同步手段就可以保证有序性(其实我相信底层也是用的同步手段),这个规则就是 Happens-before 原则,比如

  程序的启动规则:Tread 的 start 方法先于该线程的任何动作,只有线程 start 之后才能真正运行,否则仅仅就是一个对象。

  对象的终结规则:一个对象初始化的完成先行发生于 fanalize 方法前。

  锁定规则:一个 unlock 操作要先行发生于对同一个锁的 lock 操作。

  传递规则:Happens-before 原则具有传递性。

如果一个操作无法从 Happens-before 原则推导出来,那么就无法保证有序性。

  与保证可见性一样,也可以使用 volatile 关键字、synchronized 关键字、JUC 提供的显式锁lock来保证有序性。其中 synchronized 保证有序性是利用“同一时间只有一个线程获得锁,然后执行同步方法,对共享变量进行操作”的这一特点来保证有序性;而 volatile 则直接暴力的禁止编译器和 CPU 的任何指令重排序,因此保证了有序性。

  在了解了 JMM 模型之后可以更方便学习多线程知识,特别是在后面学习 volatile 关键字的时候就会显得得心应手。

原文地址:https://www.cnblogs.com/dogeLife/p/11391863.html

时间: 2024-11-12 21:25:59

多线程基础(一)— Java 内存模型的相关文章

多线程并发之java内存模型JMM

多线程概念的引入是人类又一次有效压寨计算机的体现,而且这也是非常有必要的,因为一般运算过程中涉及到数据的读取,例如从磁盘.其他系统.数据库等,CPU的运算速度与数据读取速度有一个严重的不平衡,期间如果按一条线程执行将会在很多节点产生阻塞,使计算效率低下.另外,服务器端是java最擅长的领域,作为服务器必须要能同时响应多个客户端的请求,同样需要多线程的支持.在多线程情况下,高并发将带来数据的共享与竞争问题,tomcat作为中间件将多线程并发等细节尽量封装起来处理,使用户对多线程透明,更多地关注业务

【Java并发基础】Java内存模型解决有序性和可见性

前言 解决并发编程中的可见性和有序性问题最直接的方法就是禁用CPU缓存和编译器的优化.但是,禁用这两者又会影响程序性能.于是我们要做的是按需禁用CPU缓存和编译器的优化. 如何按需禁用CPU缓存和编译器的优化就需要提到Java内存模型.Java内存模型是一个复杂的规范.其中最为重要的便是Happens-Before规则.下面我们先介绍如何利用Happens-Before规则解决可见性和有序性问题,然后我们再扩展简单介绍下Java内存模型以及我们前篇文章提到的重排序概念. volatile 在前一

JVM学习记录-Java内存模型(二)

对于volatile型变量的特殊规则 关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制. 在处理多线程数据竞争问题时,不仅仅是可以使用synchronized关键字来实现,使用volatile也可以实现. Java内存模型对volatitle专门定义了一些特殊的访问规则,当一个变量被定义为volatile时,它将具备以下两个特性: 第一个是保证此变量对所有线程的可见性,这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的.而普通变量不能做到这

Java多线程-Java内存模型

以下内容转自http://ifeve.com/java-memory-model-6/: Java内存模型规范了Java虚拟机与计算机内存是如何协同工作的.Java虚拟机是一个完整的计算机的一个模型,因此这个模型自然也包含一个内存模型——又称为Java内存模型. 如果你想设计表现良好的并发程序,理解Java内存模型是非常重要的.Java内存模型规定了如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量. 原始的Java内存模型存在一些不足,因此Java内存模型在

java内存模型-基础

基础 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信. 同步是指程序用于控制不同线程之间操作发生相对顺序的机制.在共享内

Java内存模型的基础

Java内存模型的基础 并发编程模型的两个关键问题 在并发编程中,需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信.在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息来显式进行通信. 同步是指程序中用于控制不同线程间操作发生相对顺序的机制.在

深入理解Java内存模型(1 ) -- 基础(转载)

原文地址:http://www.infoq.com/cn/articles/java-memory-model-1 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程之间没有公共状态,线

深入理解Java内存模型(一)——基础

并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信. 同步是指程序用于控制不同线程之间操作发生相对顺序的机制.在共享内存并发

【转】深入理解Java内存模型(一)——基础

并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信. 同步是指程序用于控制不同线程之间操作发生相对顺序的机制.在共享内存并发