Java内存模型
Posted simple-ly
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java内存模型相关的知识,希望对你有一定的参考价值。
线程间如何通信以及如何同步?
线程通信
通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。
-
共享内存
在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。
- 消息传递
线程之间没有公共状态,线程之间必须通过发送消息来显式进行通信。
线程同步
同步是指程序中用于控制不同线程间操作发生相对顺序的机制。
有点拗口,我的理解就是多线程之间存在并发问题,线程之间很难保持顺序,就像车道上的汽车,有些快有些慢,同步就是由多车道变为单车道,使线程并行执行改为串行执行,串行后能保证线程的顺序。
在共享内存并发模型里,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。
就是使用synchronized或者ReentrantLock。
在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。
Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。
内存模型
java的堆内存是线程共享的。堆内存中存放着类的实例域、静态域和数组元素。
图不一定正确,只是按目前的理解画的,要看完《深入了解Java虚拟机》才能对内存模型有更深入的理解。
局部变量(Local Variables),方法定义参数(Java语言规范称之为Formal Method Parameters)和异常处理器参数(Exception Handler Parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。
Java线程之间的通信由Java内存模型本文简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。
线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。
抽象结构示意图(出自《Java并发编程艺术》)
线程A与线程B通信,线程A与线程B都会在各自的本地内存中缓存一份主内存中的共享变量副本,线程A修改共享变量的值,线程B会感知到主内存中的共享变量发生变化,从而将修改后的值重新读取到线程B的本地内存中从而达到线程通信的目的。
重排序
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。
1)编译器优化重排序:
在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
2)指令级并行的重排序:
现代处理器采用了指令级并行技术(Instruction-LevelParallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
3)内存系统的重排序:
由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
Java编译器在生成指令序列时,插入特定类型的内存屏障(Memory Barriers,Intel称之为Memory Fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序。
数据依赖性
两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。而重排序是不会改变存在数据依赖关系的两个操作的执行顺序的。
数据依赖性
happens-before
在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。
happens-before只是描述内存可见性的,不是指前一个操作必须在后一个操作之前执行。
- 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
- 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
- volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
- 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
参考:
《Java编发编程艺术》
以上是关于Java内存模型的主要内容,如果未能解决你的问题,请参考以下文章