java内存模型与线程(转) good

 2019-12-10 15:50  阅读(677)
文章分类:Java Core

java内存模型与线程

参考

http://baike.baidu.com/view/8657411.htm

http://developer.51cto.com/art/201309/410971_all.htm

http://www.cnblogs.com/skywang12345/p/3447546.html

计算机的CPU计算能力超强,其计算速度与 内存等存储 和通讯子系统的速度相比快了几个数量级,

数据加载到内存中后,cpu处理器运算处理时,大部分时间花在等待获取去获取磁盘IO、网络通讯、数据库访问返回的数据上。

为什么需要Java Memory Model即java内存模型

目的只有一个:充分利用计算机的各种计算、存储、通信的能力,让ta为人类做更多的事情!
~CPU使用率 90%以上!

从物理计算机说起:
“做更多的事情”-》让计算机同时做几件事情,即并发执行若干计算任务!
为什么可以并发?
CPU的处理速度是内存的好几倍,而磁盘和网络的速度就更差了
计算机的处理器CPU在计算时,很大一部分时间花在从内存或 磁盘、网络 取 实际数 上!
这一段等待时间可以来处理其他的任务,现在计算机及操作系统都是多任务处理系统!

矛盾: CPU的处理速度比访问内存的速度快3个数量级以上!!!

怎么办:引入 高速缓存 ,在CPU附近,放置速度稍逊CPU的高速缓存,将频繁使用的数据从内存中保存到高速缓存里,让CPU尽可能的告诉运算而减少等待,运算完毕 将结果 回存 到 内存中!

基于高速缓存的架构平衡了CPU和内存间的速度差距

现代多处理器一般抽象架构如下:

20191210001352\_1.png

上图,多处理器之间 传递交换数据得通过公共的内存区,
这是并发处理时,并发实体间通信的一种模式:基于共享内存
还有的是基于消息(信号量?)的。

新问题:如果公共内存区的某个变量(共享变量)同时被多个处理器 并发操作(RW)时,就会出现数据不一致的问题:
取数据时有可能取到过期的数据,回存数据时,到底以哪个处理器缓存中的数据为准!!!

此外,CPU会对输入的指令进行 乱序执行优化,因此程序代码的出现顺序不一定是其执行顺序!

解决办法:缓存一致性协议(各个硬件平台架构有各自的实现:MSI、MESI、MOSI、Dragon Protocal等)

那jvm既然是一台虚拟的计算机,那也应该能并发处理任务!

于是就有了JMM,java内存模型就是为了屏蔽掉各种硬件和操作系统的内存访问差异,实现java语言在各个平台下都能达到高效、正确、一致的并发处理。
类比多处理器(多核)内存模型,JMM的抽象图如下:
20191210001352\_2.png

类比:

多核机 JMM 内存
多核机 JMM 内存
一个处理器 一个线程 os级别的线程(轻量级进程)
高速缓存 工作内存 jvm堆栈,寄存器、高速缓存
缓存一致性协议 多线程同步规则 os级别的调控
内存 主内存 javaheap堆,物理内存

java内存模型是指 定义的一套 jvm中变量在 工作内存和主内存 之间的 交互操作 和 操作规则 !
变量:实例字段、静态字段、构成数组对象的成员

JMM将多线程使用的内存分为共享的主内存和线程私有的工作内存,并规定:
所有变量存储于主内存区,变量的生灭都在主内存中
单线程保存需要用到的主内存变量的副本到其工作内存进行操作,不得直接操控读写主内存中的变量
线程只能看到自己工作内存中的变量,线程间数据交换 必须通过 主内存-共享内存的方式!

JMM中的8种变量操作及规则

工作内存和主内存的交互操作一共8种(JSR-133):

操作 作用的变量 效果
操作 作用的变量 效果
lock 主内存 把变量标识为每条线程独占
unlock 主内存 释放某线程的锁
read 主内存 把变量值传输到线程的工作内存
load 工作内存 把传输过来的变量存为本地副本
use 工作内存 将本地变量值传给执行引擎
assign 工作内存 把执行引擎的值写到本地变量
store 工作内存 将本地变量值传输到主内存
write 主内存 将传过来的变量值写入主内存同名变量中

附在这 8个操作上的交互规则(要熟记):

  • read&load 和 store&write这两对操作必须同时出现但两个操作间可以有其他操作,不允许不接受!
  • 一个线程如果有assign操作,则其后必须出现 store和write操作,反之不能出现。改变变量必须回存
  • 新变量只能生灭于主内存,use和store变量前,必须有对应的load或assign该变量的操作
  • 一个变量同一时刻最多允许被一个线程对其lock,同一线程可对这个变量进行多次lock,执行同样次数的unlock才能完全解锁
  • lock变量时,会清空工作内存中变量副本的值,执行引擎使用前需重新load或assign
  • 没有lock的变量不允许unlock
  • 执行unlock前必须回存至主内存,即 store和write

特殊的volatile型变量

变量可以用volatile修饰 如 public static void int race = 0;

那么volatile的语义是啥?

  • jvm中最轻量级的同步保证
  • 保证此变量对所有线程的可见性,变更时线程都能立即感知
  • 禁止jvm对该变量进行指令的重排序(WithinThread As-if-Serial Semantics线程内表现为串行)

解析:被volatile修饰的变量,jvm每次使用use前必须先从主内存中刷来最新值,而且如果有assign操作则必须立刻执行store和write操作,即刻回存主内存中,保证其他线程可以看到当前线程对变量的更改,jvm会插入内存屏蔽指令(memory fence memory barrier)来保障该变量的赋值顺序与程序输入时位置一致,即不会被重排优化。

普通变量的use和assign则没有“每次”和“立即回存”的约束,因此可能混乱!

volatile大多时候比synchronized开销低,以下场景推荐使用volatile,其余请使用synchronized等保障:

  • 变量只被单一线程修改,其他线程读取;
  • 运算结果并不依赖变量的当前值
  • 变量不需要与其他的状态变量共同参与不变约束?

Java内存模型的三个特性

上述操作的讲解其实是jmm围绕并发处理的三个基本保障点:

原子性

什么是原子性:指一个操作,或一系列操作,要么全部执行,要么全没执行!
原子性是要确保你将获得这个变量的初始值或者某个线程对这个变量完全写入之后的值;而不会是两个或更多线程在同一时间对这个变量写入之后产生混乱的结果值(即原子性可以确保,获取到的结果值所对应的所有bit位,全部都是由单个线程写入的
jvm中基本数据类型的读写是原子的
更大范围的原子性保证使用synchronized关键字包裹

内存可见性

可见性是指在一个线程中修改了共享变量本地副本的值,其他线程能立即得知这个修改!assign后主动立即store和write,use前必须read和load
三种实现方式:volatile synchronized(unlock前必须回存store-write) final

有序性

在单个线程内所有操作都是有序的:Within-Thread As-if-Serial Semantics

而从一个线程看另一个线程(虽然无法直接感知),则所有操作是无序的:指令重排和工作内存主内存同步延迟

volatile和synchronized(一个变量在同一时刻只允许一个线程lock,这样持对同一变量进行lock的多个同步块只能串行进入)都有保障这点

先行发生原则happen-before

除了volatile,synchronized final这三个关键字对上述三性的保证,jvm还默认提供了规则来保障,否则到处是那三个关键字。

这些默认的规则称为 happen-before原则,是判断数据是否存在竞争,线程是否安全的主要依据!

什么是先行发生:A操作的后果(修改变量值、发送消息、调用了方法)能被B操作感知到!与时间先后几乎无关

先行感知,先行知道,预先发生,不符先行原则的指令很有可能会被jvm执行重排优化。

主要先行感知原则:

  • 程序次序规则program order:同一线程内,按代码、和代码控制流顺序,书写在前的先行于书写在后的
  • 管程锁定规则Monitor lock:一个unlock先行于其时间上之后的对同一个锁的lock操作
  • volatile规则:对volatile变量的写操作先行于后面对他的读操作
  • 线程启动规则:thread对象的start()先行于此对象的其他动作!
  • 线程终止规则:thread的其他动作都先行于对此线程的终止检测。即先行于 Thread.join()|isAlive()
  • 线程中断规则:对线程interrupt()的调用先行于对被中断线程的检测到中断事件的发生,即先行于Thread.interrupted()
  • 对象终结规则:一个对象的初始化完成(构造函数完毕)先行于其finalize()方法
  • 传递性:A先行于B,B先行于C,则 A先行于C

时间上的先后顺序 与 先生发生原则之间没有太大关系,衡量并发安全问题的时候以 先行发生原则为准!

Java语言的线程实现

java线程

比进程更轻量的调度执行单元

java被native声明的方法都是平台相关的,不过也是最高效的

线程实现方式,看是谁来掌控线程的调度切换:
操作系统内核级别的多线程调度支持-轻量级进程(内核线程)1:1,一个进程:一个线程
用户态自行掌控线程,1:N,一个进程:多个线程
用户线程+轻量级进程,可以M:N,多对多

sun jdk的window和Linux版采用平台相关的os级一对一的轻进程模型。

并发的表现方式:单进程多线程jvm 多进程单线程php 多进程多线程powerpc

线程调度

调度:系统为线程分配处理器使用权的过程!

协同式:cooperative

线程本身控制执行时间,完成任务后,要主动通知系统切换到另一个线程上去,不稳定!

抢占式:Preemptive

os来分配线程的执行时间和切换,执行时间相对可控。Java线程如此调度,线程的优先级得由os的线程优先级保证。

线程状态转换

java定义了线程的5种状态:new runable waiting ,timed waiting , blocked,terminated

http://my.oschina.net/mingdongcheng/blog/139263

20191210001352\_3.png

http://my.oschina.net/jingxing05/blog/275334

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> java内存模型与线程(转) good

相关推荐