我是靠谱客的博主 含糊大神,最近开发中收集的这篇文章主要介绍Java中的并发机制Java内存模型,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Java内存模型

Java线程之间的通信是完全透明的,内存可见性问题很容易出现,本系类文章重点分享一下Java的内存模型相关的知识。

Java线程的通信与同步机制

在并发编程中,需要处理的两个关键问题是:线程之间的通信机制和线程之间的同步机制。通信是指线程之间如何进行交换信息,同步是指程序中用于控制不同线程之间操作发生相对顺序的机制。在命令式编程中有两种实现机制:基于共享内存和消息传递。
在共享内存的并发模型中,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信,而它的同步是要显式的进行。程序员需要显式指定某个方法或者某段代码需要在线程之间互斥执行。
在消息传递的并发模型中,线程之间没有公共状态,线程之间必须通过发送消息来显示进行通信,由于消息的发送必须在消息的接收之前,因此其同步方法是隐式的。
Java的并发采用的是共享内存模型,所以要写好Java并发程序需要理解Java线程的隐式通信机制和同步方法。

Java内存模型的抽象结构

在Java中,所有实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间是共享的。局部变量、方法参数和异常处理器参数为线程私有的,它们不会有内存可见性问题。
Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见,从抽象角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个本地内存,本地内存中存储了该线程以读/写共享变量的副本。而本地线程是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。
在这里插入图片描述
从图上看出,线程A与线程B之间要通信的话,必须经历两个步骤:
(1)线程A把本地内存中更新过的共享变量刷新到主内存中去。
(2)线程B到主内存中获取线程A之前已更新过的共享变量。
所以JMM正是通过控制主内存与每个线程的本地内存之间的交互,来为Java程序提供内存可见性的保证。

指令与指令重排序

指令是class文件编译后的Java虚拟机指令,每条指令包括操作码和变长指令组成。其中操作码用一个字节表示,也就是说指令码最多有256条,为了便于记忆java虚拟机规范为每个操作码都指定了一个助记符。此外由于操作数栈和局部变量表只是存放数据的值,指令必须知道自己操作什么类型的数据,因此操作码的助记符也提供了类型信息的提示,如iadd指令就是对int值进行加法操作。助记符首字符与变量类型关系如下所示:
在这里插入图片描述
在指令的执行过程中Java虚拟机为提升执行效率会进行指令的重排序,也就是在并发环境中程序的执行可以说是混乱的。为保证程序的并发一致性JMM为程序的操作定义了一个偏序关系,即Happens-Before规则。如果要保证两个操作的相互的可见性(如操作B的线程能看到操作A的结果),那么这两个操作之间必须满足Happens-Before规则,如果两个操作之间缺乏Happens-Before规则,那么JVM就可能对这两个操作进行指令重排序。Happens-Before规则如下表示:

  • 程序顺序规则。程序中操作A在操作B之前,那么线程中A操作将于B操作之前执行
  • 监视器锁规则。在监视器上的解锁操作必须在同一个监视器上的加锁操作之前执行
  • volatile变量规则。对volatile变量的写必须在该变量的读之前执行
  • 线程启动规则。线程中对start的调用必须在该线程中其他操作之前执行
  • 线程结束规则。线程中的其他操作必须在线程执行结束前执行
  • 中断规则。当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行
  • 终结器规则。对象的构造函数必须在启动该对象的终结器之前执行
  • 传递规则。如果操作A在操作B之前执行,并且操作B又在操作C之前执行,那么操作A必须在操作C之前执行
    通常我们需要将Happens-Before规则与同步机制(如锁的获取与释放、volatile变量的读取与写入操作)结合起来,从而对某个未被锁保护变量的访问操作进行排序。

最后

以上就是含糊大神为你收集整理的Java中的并发机制Java内存模型的全部内容,希望文章能够帮你解决Java中的并发机制Java内存模型所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(83)

评论列表共有 0 条评论

立即
投稿
返回
顶部