多线程并发艺术(一)原子性、可见性和有序性
2019-04-19 19:55
183 查看
原子性,可见性和有序性
一、原子性 Atomicity
- 原子性: 在化学上,原子就是构成一般物质最小的单位,在化学中是不可分割的。在java中,原子性就是操作不可中断的。即使是多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
原子性是操作原子性,根本上来说只是某个变量的某个操作赋予原子性
《thinking in Java》的第21章的《并发》有写: “除了long和double类型,Java基本数据类型都是的简单读写都是原子的,而简单读写就是赋值和return语句。” 因此而对于其他自增自减以及其他运算操作,是非原子操作。
基本数据类型的自增自减操不是原子操作,这是为什么呢
比如:i++; 1)读取i的值 2)进行 i 增加1 的操作 3)将i的值新写入内存
这已经是三个步骤了。原子操作可是不能拆分的,这一下子就拆分了三个子操作。所以很明显的不是原子操作。
虚拟机定义了8种原子操作,包括
lock 锁定主内存的变量,使其被某一线程独占 unlock 解锁主内存中的变量 read 把一个主内存的变量传递到工作内存中,以便load load 将从主内存传递的值传递到工作内存的变量副本中 store 将工作内存中变量副本传递到主内存中去,以便write write 将工作内存传递过来的值赋到主内存中变量 use 将工作内存的值传递给执行引擎 assign 将执行引擎的值传递到工作内存
这8种操作可以用来确定你的访问是否安全。
这八种操作涉及到了Java内存模型
二、Java内存模型 Java Memory Model
- 在jdk1.2之前,java的内存模型实现总是从主内存(共享内容)中读取变量。由于CPU执行速度很快,而从内存中读取数据和向内存中写入数据的过程和CPU执行速度相比慢的太多了。大大降低了指令执行的速度。所以现在的Java内存模型下,线程可以把变量保存在本地内存中,而不是直接在主内存中进行读写。
- JMM主要用于屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。如果扩展开来说,它也可以称为内存一致性模型。
如图就是多个线程中共享一块主内存,每个线程中有自己的一块工作内存。线程的工作内存中保存了该线程使用的变量的主内存的副本拷贝,线程对变量的所有操作(读取,赋值等)都在工作内存中进行,而不能直接读写主内存的变量。不同的线程之间无法直接访问对方工作内存中的变量,线程之间值的传递都间接通过主内存来完成
注意:Java内存模型本身是一种抽象的概念,并不真实存在,它描述的是一组规则和规范,通过这组规范定义了程序中各个变量的访问方式。 而Java内存区域的划分(方法区,堆,栈等)是实际的区域划分。 后续我们也会有相应的章节去详细说明Java内存区域的划分。
三、可见性 Visibility
- 可见性:当一个线程修改了主内存中的共享变量的时候,对于其他线程是可见的。(即修一个线程改了工作内存中的共享变量副本时,其他线程要知道你修改了这个共享变量副本)
- 普通变量很明显是不能实现可见性的。但是volatitle和synchorized,final是可以实现的。
- 后续会有章节专门说明这三个关键字。
四、有序性 Ordering
- 有序性:程序运行的顺序与代码的先后顺序一致。
- Java内存模型中的程序天热有序性可以总结为一句话:
如果在本线程内观察,所有操作都是有序的。如果在一个线程中观察另一个线程,所有操作都是无序的
前半句指的是线程内表现为串性语义,后半句指的是指令重排序和工作内存与主内存同步延迟
- 有序性的语意大致有几层
1) 最常见的是 保证多线程的串性机制
2)防止指令重排序
3)程序运行的先后顺序,比如JMM定义的一些Happens-before原则。
那么什么是指令重排序呢
五、指令重排序 Instroduction Reorder
int a = 1; int b = 3; a = 4; //代码1 b = 5; //代码2
如上述代码,在代码顺序中,代码1是在代码2之前的。那么JVM在调用这些代码的时候,真的会保证代码1在代码2之前运行么?不一定,这是为什么呢,因为这里发生了 指令重排序(Instroduction Reorder).
计算机在执行过程中,为了提高性能,编译器和处理器常常会对指令进行重排序
一般分为以下三种:
- 编译器优化重排序:编译器在不改变单线程线程语义的情况下,可以重新安排语句的执行顺序(遵循as-if-serial原则)。
as-if-serial语义:不管怎么重排序(编译器和处理器为了提高并发量),(单线程)的执行结果不会改变
- 指令级并行重排序:现代处理器采用了指令级并行技术,将多条指令重叠执行,如果不存在数据依赖关系(即后一个执行的语句无序依赖前面执行的语句的结果),处理器可以改变语句对应的机器指令的顺序
int a = 1; //代码1 int b = 3; //代码2 a = a + 3; //代码3 b = a * a; //代码4
代码4是不可能在代码3之前运行的,因为它依赖了代码3的运行结果
- 内存系统重排序:由于处理器使用缓存和读写缓存冲区,这使得加载(load)和存储(store)操作看上去像是乱序执行,因为三级缓存的存在,导致内存和缓存的数据同步存在时间差。
有兴趣的可以参考下述博文
指令重排序的详细说明
理解了Java内存模型中的原子性,可见性和有序性,下一章我将再详细介绍一个有趣的修饰符 volatitle。
注意:它可以实现可见性和有序性,但是不可以原子性
相关文章推荐
- Java多线程总结(5)— 原子性、可见性、有序性和并发库的原子性操作
- 多线程学习四:并发编程中的原子性、可见性、有序性
- 并发研究之可见性、有序性、原子性
- Java并发12:并发三特性-原子性、可见性和有序性概述及问题示例
- 聊聊高并发(十九)理解并发编程的几种"性" -- 可见性,有序性,原子性
- 死磕Java并发(三) 可见性、原子性和有序性
- 聊聊高并发(十九)理解并发编程的几种"性" -- 可见性,有序性,原子性
- 01-可见性、原子性和有序性问题:并发编程Bug的源头
- 多线程的三个特性:原子性、可见性、有序性
- java并发特性:原子性、可见性、有序性
- java 并发概念与内存分析,原子性、可见性、有序性
- 01.并发编程中可见性、原子性、有序性问题
- 并发1--原子性、可见性、有序性
- java并发特性,原子性、有序性、可见性
- java并发编程--原子性、可见性、有序性
- java并发之原子性、可见性、有序性
- Java多线程 之 原子性与可见性(八)
- JVM高级特性与实践(十二):高效并发时的内外存交互、三大特征(原子、可见、有序性) 与 volatile型变量特殊规则
- 多线程原子性、可见性、可排序
- Java并发_volatile实现可见性但不保证原子性