b2科目四模拟试题多少题驾考考爆了怎么补救
b2科目四模拟试题多少题 驾考考爆了怎么补救

【教程】终于有人把Java内存模型说清楚了!(2)

电脑杂谈  发布时间:2019-06-30 12:11:59  来源:网络整理

除了现在很多流行的处理器会对代码进行优化乱序处理,很多编程语言的编译器也会有类似的优化,比如 Java 虚拟机的即时编译器(JIT)也会做指令重排。

可想而知,如果任由处理器优化和编译器对指令重排的话,就可能导致各种各样的问题。

关于员工组织调整的情况,如果允许人事部在接到多个命令后进行随意拆分乱序执行或者重排的话,那么对于这个员工以及这家公司的影响是非常大的。

并发编程的问题

前面说的和硬件有关的概念你可能听得有点蒙,还不知道他到底和软件有啥关系。

但是关于并发编程的问题你应该有所了解了,比如原子性问题,可见性问题和有序性问题。

“从 p6 处理器开始,如果指令访问的内存区域已经存在于处理器的内部缓存中,则“lock” 前缀并不将引线 lock 的电位拉低,而是锁住本处理器的内部缓存,然后依靠缓存一致性协议保证操作的原子性。 为在多cpu环境中利用test_and_set指令实现进程互斥,硬件需要提供进 一步的支持,以保证test_and_set指令执行的原子性. 这种支持目前多以 “锁总线”(bus locking)的形式提供的,由于test_and_set指令对内存的两 次操作都需要经过总线,在执行test_and_set指令之前锁住总线,在执行 test_and_set指令后开放总线,即可保证test_and_set指令执行的原子性, 用法如下: 多处理机互斥算法。写-替换模式提供了原子性,因为底层的os.rename()是原子性的。

这里简单回顾下这三个问题,我们说,并发编程,为了保证数据的安全,需要满足以下三个特性:

有没有发现,缓存一致性问题其实就是可见性问题。而处理器优化是可以导致原子性问题的。指令重排即会导致有序性问题。

所以,后文将不再提起硬件层面的那些概念,而是直接使用大家熟悉的原子性、可见性和有序性。

什么是内存模型

由于不同硬件架构的处理器之间的指令不能相互通用,但是在功能上又有很多一致的地方,都能对一定长度的二进制数进行各种运算操作,比如加法指令,在x86硬件系统中有,在mips硬件系统中也有,虽然具体的硬件实现上有所不同,指令的二进制代码也不同,但对人们而言作用是共同的,于是我们把这些相同的功能进行抽象,并制定相应的一套语法系统,让编译器能把一组相同的代码针对不同的硬件编译出不同的二进制代码,就开发出了当前的中间语言。不过intel为了降低这种设计缺陷带来的影响,在core架构每个核心分别内建一组指令及二组数据预先撷取器,而共享的l2缓存控制器内建两组、可动态分配至不同的核心的数据预先撷取器,可根据应用程序数据的行为,进行指令与数据的预先撷取动作,让所需要的内存地址数据,尽量存放在缓存之中,减少存取内存的次数,这样的设计有效地提高了系统性能。但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。

最简单直接的做法就是废除处理器和处理器的优化技术、废除 CPU 缓存,让 CPU 直接和主存交互。

但是,这么做虽然可以保证多线程下的并发问题。但是,这就有点因噎废食了。

所以,为了保证并发编程中可以满足原子性、可见性及有序性。有一个重要的概念,那就是——内存模型。

ecc内存和普通内存区别_双内存和单内存区别_主存和内存的区别

由2.1可知,要编写正确的并发程序,关键问题在于:在访问共享的可变状态时需要进行正确的管理,这里正确的管理主要解决竞态条件和数据竞争(主要包括对象可见性)问题,我们知道同步方法和同步代码可以确保以原子的方式执行操作,因此错误的会认为synchronized只能用于实现原子性和确定临界区(critical section)即解决竞态条件问题,其实同步还有重要的一方面就是内存(对象)的可见性(visibly)。java内存模型要求变量的读取和写入操作必须是原子性的,但对于非volatile类型的double和long变量,jvm允许将64为读或写操作分解成两个32位操作。java线程之间的通信总是隐式进行 java并发模型—硬件视图 内存空间 共享对象 共享对象 共享对象 内存中的jvm 对象 程之间共享 线程1 线程2 处理器a 处理器b 处理器c 处理器d java并发模型—操作系统视图 jvm进程 hotspot vm中, java线程被 java线程 java线程 java线程 映射为本地操作系 统线程 linux kernel 操作系统内核 直接调度java 线程给可用的cpu处理器a 处理器b 处理器c 处理器d 编译器和处理器喜欢不择手段的冒险源代码 编译器优化 指令级并行 内存系统的 最终执行的 的重排序 的重排序 重排序 指令序列 编译器的 重排序 重排序 指令级并行的 处理器的 重排序 重排序 内存系统的 重排序 顺序一致性内存模型的原型结构 处理器a 处理器b 处理器c 处理器c a3 b2 d1 程序顺 a2 b1 c3 d2 序不变 a1 c2 每个内存 c1 单元一个 fifo队列内存单元 1 2 3 4 5 6 7 8 9 10 11 12 13 … … n顺序一致性内存模型的程序员视图 线程 线程 线程 线程 … 2 3 n 1 内存 顺序一致性内存模型的2 大特性 特性2 线程a 的程序顺序 操作的执行整体上无序,但两个线程 都只能看到这个执行顺序。

通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。

“从 p6 处理器开始,如果指令访问的内存区域已经存在于处理器的内部缓存中,则“lock” 前缀并不将引线 lock 的电位拉低,而是锁住本处理器的内部缓存,然后依靠缓存一致性协议保证操作的原子性。我们要记住的是总线是一种共享的资源,如果不合理的使用,比如聊聊高并发(五)理解缓存一致性协议以及对并发编程的影响 这篇中说的缓存一致性协议导致的总线流量风暴,会影响程序执行的效率。由2.1可知,要编写正确的并发程序,关键问题在于:在访问共享的可变状态时需要进行正确的管理,这里正确的管理主要解决竞态条件和数据竞争(主要包括对象可见性)问题,我们知道同步方法和同步代码可以确保以原子的方式执行操作,因此错误的会认为synchronized只能用于实现原子性和确定临界区(critical section)即解决竞态条件问题,其实同步还有重要的一方面就是内存(对象)的可见性(visibly)。

内存模型解决并发问题主要采用两种方式:

本文就不深入底层原理来展开介绍了,感兴趣的朋友可以自行学习。

什么是 Java 内存模型

前面介绍了计算机内存模型,这是解决多线程场景下并发问题的一个重要规范。

那么具体的实现是如何的呢?不同的编程语言,在实现上可能有所不同。

我们知道,Java 程序是需要运行在 Java 虚拟机上面的,Java 内存模型(Java Memory Model,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了 Java 程序在各种平台下对内存的访问都能保证效果一致的机制及规范。

java内存模型,指java程序在运行时内存的模型,而java代码运行在java虚拟机上,所以也就指java虚拟机的运行时内存模型。描述的是java方法执行的内存模型,每个方法在执行的同时会创建一个栈帧用于存储局部变量表什么的。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。

感兴趣的可以参看下这份PDF文档:

~pugh/java/memoryModel/jsr133.pdf

Java 内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存。

类的状态,也就是类之间共享的变量,是存储在主内存中的,每次java线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在自己的工作内存中有一份拷贝,运行自己线程代码的时候,用到这些变量,操作的都是自己工作内存中的那一份。每个线程在获取锁之后会在自己的工作内存来操作共享变量,操作完成之后将工作内存中的副本回写到主内存,并且在其它线程从主内存将变量同步回自己的工作内存之前,共享变量的改变对其是不可见的。关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,java内存模型定义了以下八种操作来完成:。

不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。

而 JMM 就作用于工作内存和主存之间数据同步过程。它规定了如何做数据同步以及什么时候做数据同步。

这里面提到的主内存和工作内存,读者可以简单的类比成计算机内存模型中的主存和缓存的概念。

特别需要注意的是,主内存和工作内存与 JVM 内存结构中的 Java 堆、栈、方法区等并不是同一个层次的内存划分,无法直接类比。

《深入理解Java虚拟机》中认为:如果一定要勉强对应起来的话,从变量、主内存、工作内存的定义来看,主内存主要对应于 Java 堆中的对象实例数据部分。而工作内存则对应于虚拟机栈中的部分区域。

java线程之间的通信总是隐式进行 java并发模型—硬件视图 内存空间 共享对象 共享对象 共享对象 内存中的jvm 对象 程之间共享 线程1 线程2 处理器a 处理器b 处理器c 处理器d java并发模型—操作系统视图 jvm进程 hotspot vm中, java线程被 java线程 java线程 java线程 映射为本地操作系 统线程 linux kernel 操作系统内核 直接调度java 线程给可用的cpu处理器a 处理器b 处理器c 处理器d 编译器和处理器喜欢不择手段的冒险源代码 编译器优化 指令级并行 内存系统的 最终执行的 的重排序 的重排序 重排序 指令序列 编译器的 重排序 重排序 指令级并行的 处理器的 重排序 重排序 内存系统的 重排序 顺序一致性内存模型的原型结构 处理器a 处理器b 处理器c 处理器c a3 b2 d1 程序顺 a2 b1 c3 d2 序不变 a1 c2 每个内存 c1 单元一个 fifo队列内存单元 1 2 3 4 5 6 7 8 9 10 11 12 13 … … n顺序一致性内存模型的程序员视图 线程 线程 线程 线程 … 2 3 n 1 内存 顺序一致性内存模型的2 大特性 特性2 线程a 的程序顺序 操作的执行整体上无序,但两个线程 都只能看到这个执行顺序。然后执行线程代码,这里包括共享变量的读操作和写操作,因为写操作会导致多个处理器处理的数据不一致,所以cas上场了,它要求写操作的时候,检查要写的共享变量cache和内存是否一致,若一致则继续执行写操作,若不一致则重新load线程上下文,重新执行。多线程是一个进程里面包含多个线程,他们共享进程里面的资源和内存空间,所以多个线程可以通过全局变量进行通信。

目的是保证并发编程场景中的原子性、可见性和有序性。

Java 内存模型的实现

主存和内存的区别_双内存和单内存区别_ecc内存和普通内存区别

由上面的分析我们知道,同步容器并不能保证多线程安全,而并发容器是针对多个线程并发访问而设计的,在jdk5.0引入了concurrent包,其中提供了很多并发容器,极大的提升同步容器类的性能。这就引出了thread和event之争,因为前者就是完全用线程来处理并发,后者是用事件驱动来处理并发。加入gil主要的原因是为了降低程序的开发的复杂度,比如现在的你写python不需要关心内存回收的问题主存和内存的区别,因为python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题,这可以说是python早期版本的遗留问题。


本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-109489-2.html

相关阅读
    发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

    热点图片
    拼命载入中...