Java并发篇_Java内存模型

在并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题。那么它们产生的原因和在Java中解决的办法又是什么呢?

一、内存模型的相关概念

​ 计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。因此在CPU里面就有了高速缓存。

也就是,当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。

简单的例子,比如下面的这段代码:

i = i + 1;

当线程执行这个语句时,会先从主存当中读取i的值,然后复制一份到高速缓存当中,然后CPU执行指令对i进行加1操作,然后将数据写入高速缓存,最后将高速缓存中i最新的值刷新到主存当中。

这个代码在单线程中运行是没有任何问题的,但是在多线程中运行就会有问题了。在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存

比如同时有2个线程执行这段代码,假如初始时i的值为0,那么我们希望两个线程执行完之后i的值变为2。但是事实会是这样吗?

可能存在下面一种情况:初始时,两个线程分别读取i的值存入各自所在的CPU的高速缓存当中,然后线程1进行加1操作,然后把i的最新值1写入到内存。此时线程2的高速缓存当中i的值还是0,进行加1操作之后,i的值为1,然后线程2把i的值写入内存。

最终结果i的值是1,而不是2。

这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。

为了解决缓存不一致性问题,通常来说有以下2种解决方法:

1)通过在总线加LOCK#锁的方式

在早期的CPU当中,是通过在总线上加LOCK#锁的形式来解决缓存不一致的问题。因为CPU和其他部件进行通信都是通过总线来进行的,如果对总线加LOCK#锁的话,也就是说阻塞了其他CPU对其他部件访问(如内存),从而使得只能有一个CPU能使用这个变量的内存。比如上面例子中如果一个线程在执行 i = i +1,如果在执行这段代码的过程中,在总线上发出了LCOK#锁的信号,那么只有等待这段代码完全执行完毕之后,其他CPU才能从变量i所在的内存读取变量,然后进行相应的操作。这样就解决了缓存不一致的问题。

2)通过缓存一致性协议

加LOCK#锁的方式会有一个问题,由于在锁住总线期间,其他CPU无法访问内存,导致效率低下。

所以就出现了缓存一致性协议。最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

img

二、并发编程的三个概念存在的问题

1、 线程切换带来的原子性问题

Java中的一条语句,在翻译为机器码之后,可能对应的是多个指令。

比如:i++这个操作至少需要3条指令;

  1. 把 i 的值从内存=加载到寄存器;
  2. 执行+1操作;
  3. 把值写入内存;

假如 i=0,两个线程同时执行该操作,可能线程1执行完第一步,就切换到线程2执行,本来两个线程各执行一次后 i 的值应该为 2 ,此时就出现 两次递增操作后值为 1 的现象;

2、缓存导致的可见性问题:

Java内存模型规定所有的变量存储在主内存中。每个线程都有自己的工作内存,线程在工作内存中保存了使用到的主内存中变量的副本拷贝,线程对变量的操作必须在工作内存中进行,不能直接读写主内存中的变量。不同线程之间无法访问对方工作内存的变量。线程之间共享变量值的传递均需要通过主内存来完成。

当线程1对共享变量A进行修改之后,线程2的工作内存中A可能还不是最新的值。这时候线程1的操作对线程2就不具有可见性。

3、编译优化带来的有序性问题:

为了充分利用处理器的性能,处理器会对输入的代码进行乱序执行。在计算之后将乱序执行的结果重组,并保证该结果和顺序执行的结果一致,但是并不保证程序中各个语句的计算顺序和输入代码的顺序一致。Java虚拟机也有类似的指令重排序优化。

比如:Object obj = new Object(),

这条语句对应的指令为:

  1. 分配一块内存M;
  2. 在M上初始化 Object 对象;
  3. 将M的地址赋值给 obj;

计算机经过优化后可能先执行第三步,再第二步,如果执行完第三步后切换到别的线程,若此时访问该变量则会发生空指针异常;

三、Java内存模型

在前面谈到了一些关于内存模型以及并发编程中可能会出现的一些问题。下面我们来看一下Java内存模型,研究一下 Java内存模型 为我们提供了哪些保证以及在java中提供了哪些方法和机制来让我们在进行多线程编程时能够保证程序执行的正确性。

在Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。那么Java内存模型规定了哪些东西呢,它定义了程序中变量的访问规则,往大一点说是定义了程序执行的次序。

注意:为了获得较好的执行性能,Java内存模型并没有限制执行引擎使用处理器的寄存器或者高速缓存来提升指令执行速度,也没有限制编译器对指令进行重排序。也就是说,在java内存模型中,也会存在缓存一致性问题和指令重排序的问题。

Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存各自的缓存中中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。

举个简单的例子:在java中,执行下面这个语句:

i = 10

执行线程必须先在自己的工作线程中对变量i所在的缓存行进行赋值操作,然后再写入主存当中。而不是直接将数值10写入主存当中。

那么Java语言本身对原子性、可见性以及有序性提供了哪些保证呢?

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

  • 概念:多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

  • 概念:Java程序中,如果在本线程中观察,所有的操作都是有序的;如果在另一个线程观察,所有的操作都是无序的。前半句指的是线程内表现为串行的语义,后半句指的是指令重排序和主内存和工作内存同步延迟的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/535661.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java并发篇_synchronized

synchronized是Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。本文给大家介绍java中的用法。 一、为什么要使用synchronized 在并发编程中存在线程安全问题,主要原因有&…

Java并发篇_volatile

volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级&#xff…

分布式文件系统:原理、问题与方法

本地文件系统如ext3,reiserfs等(这里不讨论基于内存的文件系统),它们管理本地的磁盘存储资源、提供文件到存储位置的映射,并抽象出一套文件访问接口供用户使用。但随着互联网企业的高速发展,这些企业对数据…

JDK源码解析之java.util.Iterator和java.lang.Iterable

在Java中,我们可以对List集合进行如下几种方式的遍历:第一种就是普通的for循环,第二种为迭代器遍历,第三种是for each循环。后面两种方式涉及到Java中的iterator和iterable对象,接下来我们通过源码来看看这两个对象的区…

JDK源码解析之java.util.AbstractCollection

AbstractCollection类提供了collection的实现类应该具有的基本方法,具有一定的普适性,可以从大局上了解collection实现类的主要功能。 java.util.AbstractCollection这个类提供了对接口Collection骨骼级的实现。 一、源码解析 1、iterator():返回一个迭…

GC 垃圾回收

垃圾回收机制是由垃圾收集器Garbage Collection GC来实现的,GC是后台的守护进程。它的特别之处是它是一个低优先级进程,但是可以根据内存的使用情况动态的调整他的优先级。因此,它是在内存中低到一定限度时才会自动运行,从而实现对…

高性能MySQL(1)——MYSQL架构

MySQL最重要、最与众不同的特性是它的存储引擎架构,这种架构将查询处理与数据的存储/提取相分离,使得可以在使用时根据不同的需求来选择数据存储的方式。 一、Mysql逻辑架构 如果能在头脑中构建出一幅MySQL各组件之间如何协同工作的架构图,就…

高性能MySQL(4)——查询性能优化

査询优化、索引优化、库表结构优化需要齐头并进,一个不落。 一、为什么查询速度为变慢 在尝试编写快速的查询之前,需要清楚一点,真正重要是响应时间。如果把查询看作是一个任务,那么他由一系列子任务组成,每个子任务都会消耗一定的时间。如果…

Portainer简介及部署

一、介绍 Portainer是Docker的图形化管理工具,提供状态显示面板、应用模板快速部署、容器镜像网络数据卷的基本操作(包括上传下载镜像,创建容器等操作)、事件日志显示、容器控制台操作、Swarm集群和服务等集中管理和操作、登录用…

Nexus搭建Maven私有仓库

一、使用Docker安装Nexus 1、查询当前有哪些Nexus镜像 docker search nexus2、下载sonatype/nexus3 docker pull docker.io/sonatype/nexus33、运行nexus容器 mkdir -p /usr/local/nexus3/nexus-data #新建挂载目录 chown -R 200 /usr/local/nexus3/nexus-datadocker run -…

日常问题——VMware下的CentOS7 Ping不通百度

问题描述: 在VMware下新创建的CentOS ping 不通百度 解决方案: 1、点击VMware的编辑按钮下的虚拟网络编辑器 2、更改设置后,选择NAT类型,点击NAT设置 3、记录下,子网ip,掩码,网管IP信息 4、…

CentOS7.6下安装Ambari

一、准备工作 1.1、准备三台CentOS 1.2、配置静态IP、DNS vi /etc/sysconfig/network-scripts/ficfg-ens33IPADDR为 配置的ip NETMASK 子网掩码 GATEWAY 网关 配置保存后重启虚拟网络 service network restart1.3、配置Hostname vi /etc/hostname#将第一行替换成新名字 m…

linux系统启动流程详解

一、MBR的概念 主引导扇区位于硬盘的0磁道0柱面1扇区,共512bytes,可存放一小段程序及主分区表,由三大部分组成: 硬盘主引导记录MBR(Master Boot Record)占446bytes 分区表DPT(Disk Partition Table)占64bytes 硬盘有效标志(Magic Number)占2bytes。 硬盘…

Ambari系统架构

一、Ambari系统架构 Ambari框架采用的是Server/Client的模式,主要由两部分组成:ambari-agent和ambari-server。ambari依赖其它已经成熟的工具,例如其ambari-server 就依赖python,而ambari-agent还同时依赖ruby, puppet&#xff0…

Linux系统的基本安装

一、制作Linux安装启动盘 1.准备一个干净的U盘,格式化。 2.下载64位系统镜像:rhel-server-6.8-x86_64-dvd.iso。64位系统直接安装就好, 3.首先制作一个启动盘,解压rhel-server-6.8-i386-dvd.iso文件 4.下载安装UltraISO&#…

Shell基本概念

一、什么是shell shell是外壳的意思,就是操作系统的外壳。我们可以通过shell命令来操作和控制操作系统,比如Linux中的Shell命令就包括ls、cd、pwd等等。总结来说,Shell是一个命令解释器,它通过接受用户输入的Shell命令来启动、暂…

Linux下Java的安装与配置

0、下载好JDK安装包后上传到Linux服务器的/usr/local/java/下 1、解压tar -zxvf jdk-8u251-linux-x64.tar.gz 2、改名mv jdk1.8.0_251 jdk1.8 3、配置环境变量vi /etc/profile 添加以下内容 #JAVA export JAVA_HOME/usr/local/java/jdk1.8 export JRE_HOME$JAVA_HOME/jre e…

Spark初识-什么是Spark

Spark是一个基于内存的开源计算框架,于2009年诞生于加州大学伯克利分校AMPLab(AMP:Algorithms,Machines,People),它最初属于伯克利大学的研究性项目,后来在2010年正式开源&#xff0…

系统优化

1、关闭selinux sed -i ‘s/SELINUXenforcing/SELINUXdisabled’ setenforce 0 2.精简开机服务 ntsysv setup system service

Spark初识-Spark与Hadoop的比较

Spark,是分布式计算平台,是一个用scala语言编写的计算框架,基于内存的快速、通用、可扩展的大数据分析引擎 Hadoop,是分布式管理、存储、计算的生态系统;包括HDFS(存储)、MapReduce(…