细说Java多线程之内存可见性:学习笔记

Posted 木鲸鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了细说Java多线程之内存可见性:学习笔记相关的知识,希望对你有一定的参考价值。


木鲸鱼

生命不息,折腾不止,技术改变生活。

细说Java多线程之内存可见性:学习笔记(一)   细说Java多线程之内存可见性:学习笔记(一)

一、基础概念

工作内存
java 中的一条单个线程直接操作变量的内存。

主内存
java 中的不同线程之间共享变量的内存。

共享变量
如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。

可见性

一个线程对共享变量值的修改,能够及时地被其他线程看到。

二、内存模型(JMM)

1. 内存模型规范

所有的变量都存储在主内存中,每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)。

从规范中可以得出以下两条结论:

a. 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写。

b. 不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。

用图形来表示内存模型:

细说Java多线程之内存可见性:学习笔记(一)
java 内存模型


2. 多线程可见性实现原理

由于JMM规范的限制,要实现多线程之间的变量可见,就必须要通过主内存进行传递:

如果线程1对共享变量的修改要想被线程2及时看到,必须要经过如下两个步骤

步骤1、把工作内存1中更新过的共享变量刷新到主内存
步骤2、将主内存中最新的共享变量的值更新到工作内存2



细说Java多线程之内存可见性:学习笔记(一)


共享变量可见性实现的原理

三、指令重排序

在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。重排序分三种类型:

1. 编译器优化的重排序(编译器优化)
编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。


2. 指令级并行的重排序(处理器优化)
现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。


3. 内存系统的重排序(处理器优化)
由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

从java源代码到最终实际执行的指令序列,会分别经历下面三种重排序:

指令重排序

上述的1属于编译器重排序,2和3属于处理器重排序。这些重排序都可能会导致多线程程序出现内存可见性问题。

重排序不会给单线程带来内存可见性的问题(因为 JMM 规定单线成一定遵循 as-if-serial语义)

四、as-if-serial 语义

无论如何重排序,程序执行的结果应该与代码顺序执行的结果一致(Java编译器、运行时和处理器都会保证Java在单线程下遵循as-if-serial语义)

int num1 = 1;              // 第 1 行代码
int num2 = 2;              // 第 2 行代码
int sum = num1 + num2;    // 第 3 行代码

单线程:第1、2行的顺序可以重排,但是第3行不能和前两行一起重排序。

总结
多线程中的线程需要使用和操作主内存共享变量时,会拷贝主内存中的副本到自己的工作内存进行读写操作,这将会导致内存可见性问题:在单个线程对共享变量进行读写操作之后,如果不能及时更新主内存变量,其他线程访问并使用主内存中的变量时,会发生不可控制的变量不同步问题。

编译器和处理器会对多线程中的代码做指令重排序优化,它不会给单线程带来内存可见性问题,因为单线成遵循as-if-serial语义。

但在多线程中一定存在程序交错执行现象,由于JVM的代码优化机制使源代码进行指令重排序优化操作,势必会造成内存可见性问题。

- END -

技术改变生活,
菜鸟程序员的成长之路。
欢迎关注: 木鲸鱼

以上是关于细说Java多线程之内存可见性:学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

JavaSE学习53:细说多线程之内存可见性

多线程内存可见性

Java内存模型之可见性问题

Java多线程之内存可见性

java线程-java多线程之可见性

多线程编程 之java内存模型(JMM)可见性有序性问题解决方案