第7章 JMM 教程
2.2 JMM
2.2.1 什么是JMM
CPU与内存交互存取数据
在讲线程安全概念的时候讲到一个多个线程执行sum++导致线程不安全的例子:

造成线程不安全的原因是多线程在交替执行sum++。

为什么sum++由多条指令组成呢?这和计算机硬件结构有关,下图是CPU与内存交互存取数据的示意图:

CPU负责计算,内存负责存储,CPU计算速度要比CPU从内存中存取数据要快的多,所以硬件工程师在CPU中添加了高速缓存,CPU把数据从内存拿到高速缓存,CPU从高速缓存进行存取的速度要远远大于CPU从内存存取数据的速度,所以数据计算会先进入高速缓存,最后再将计算结果从高速缓存同步到内存。
JMM内存模型介绍
为了适应不同的硬件,Java定义了JMM内存模型,它与计算机硬件结构相似如下:

每个线程有自己的工作内存,工作内存相当于CPU高速缓存,共享内存相当于主内存,共享内存中存储了共享数据(对象的成员变量、类的静态变量等)。如果将JMM和JVM的内存模型对应,共享内存相当于堆内存,工作内存相当于栈内存。
JMM即Java内存模型(Java Memory Model ),它和JVM的内存模型不是一回事,JMM内存模型是描述多线程对共享数据读写的规则,旨在定义共享变量的读写规则保证线程安全。
JMM中定义线程对共享变量的操作有四个基础操作:read、write、lock、unlock。
read:将共享变量从共享内存读到工作内存。
write:将工作内存中的数据写入共享内存。
lock:锁定共享变量,此时共享变量被一个线程独占。
unlock: 释放锁,此时共享变量可被其它线程锁定。
详细的JMM请参考jsr133标准,需要获取jsr133英文版与中文版请关注预见猿份公众号,进入菜单“自学Java”-->"开发工具",查找并下载。
原子性可见性有序性
JMM定义了对共享变量操作的原子性、可见性、有序性:
1、原子性
原子性是指一个操作在执行过程中不会中断不会出现多线程交替执行。
比如对一个int变量的赋值操作:
a=1;
a=2;
多线程执行这两条语句,执行结果要么a=1,要么a=2,不会出现第三个结果,这说明对int变量a的赋值操作具有原子性。
对a变量执行a++操作则不具有原子性,前边学习线程安全知道多线程执行a++存在交替执行的问题,因为a++底层由多条指令组成,不具有原子性的操作会导致线程不安全。
稍后会讲解保证原子性的解决方案。
2、可见性
可见性是指:多线程访问共享变量,一个线程修改了共享变量的值,另一个线程可以立即得到共享变量的最新值,简言之:一个线程修改共享变量对其它线程可见。
因为共享内存与工作内存之间存在数据同步,一个线程修改了共享变量还没有同步到共享内存时被其它线程读取旧值此时程序出现与预期不一致的结果,即线程不安全。
比如:线程1修改共享变量a为1,线程2要在a变为1后立即读取新值,如果a的值修改后对线程2不可见就会造成线程不安全的问题。
稍后会讲解保证可见性的解决方案。
3、有序性
有序性是指一块代码的执行顺序和代码书写顺序是一致的。
什么情况下代码的执行顺序和书写顺序不一致?当出现指令重排序时则可能违反了有序性规则。
比如下边的代码:
int a=1;
int b=2;
int c = a+b;在执行时可以按如下顺序执行:
int b=2;
int a=1;
int c = a+b;这时就发生了指令重排序,指令重排序在CPU和和编译器都可能存在。
多线程程序发生了指令重排序就可能导致程序执行的预期和结果不一致,导致 线程不安全。
稍后介绍保证程序有序性的解决方案。
请免费关注 预见猿份 公众号,发送:yjoffer.com,获取解锁密码
微信扫码关注关注公众号回复获取密码后,在下方输入并点击解锁,本机后续再读无需重复输入。
