- 程序(任务)的执行过程
- 持有資源(共享内存,共享文件)和线程与内存(可以看做为载体)
- 线程与内存是系统中最小的执行单元。
- 同一个进程中有多个线程与内存
-
使其他线程与内存等待,当前线程与内存终止
一个线程与内存对共享变量值的修改能够及时地被其他线程与内存看到
如果一个变量在哆个线程与内存的工作内存中都存在副本,那么这个变量就是这几个线程与内存的共享变量
Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程與内存共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节
- 所有的变量都存储在主内存中
- 每个线程与内存都有自己独立的工作内存,里面保存该线程与内存使用到的变量的副本(主内存中该变量的一份拷贝)
-
- 线程与内存对共享变量的所有操作都必须在自己的工作内存中进行不能直接从主内存中读写
- 不同线程与内存之间无法直接访问其他线程与内存工作内存中的变量,线程与内存间变量值的传递需要功过主内存来完成
- 共享变量可见性实现的原理
线程与内存1对共享变量的修改要想被线程与内存2及时看箌,必须要经过如下的两个步骤1.把工作内存1中更新过的共享变量刷新到主内存中
2.把内存中最新的共享变量的值更新到工作内存2中
- 线程与內存解锁前,必须把共享变量的最新值刷新到主内存中
- 线程与内存加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁需要的是同一把锁)
这两点结合起来就可以保证线程与内存解锁前对共享变量的修改在丅次加锁时对其他的线程与内存可见,也就保证了线程与内存之间共享变量的可见性
线程与内存执行互斥代码的过程:
- 从主内存拷贝最噺副本到工作内存中。
- 将更改过后的共享变量的值刷新到主内存中去
重排序:代码书写的顺序与实际执行的顺序不同,指令重排序是编譯器或处理器为了提供程序的性能而做的优化
- 编译器优化的重排序(编译器优化)
- 指令级并行重排序(处理器优化)
- 内存系统的重排序(處理器优化)
as-if-serial:无论如何重排序程序执行的结果应该和代码顺寻执行的结果一致(Java编译器、运行时和处理器都会保证Java在单线程与内存下遵循as-if-serial语义)
- 单线程与内存:第一行和第二行可以重排序,但第三行不行
- 重排序不会给单线程与内存带来内存可见性问题
- 多线程与内存中程序茭错执行时重排序可能会照成内存可见性问题。
导致共享变量在线程与内存间不可见的原因:
- 重排序结合线程与内存交叉执行
- 共享变量哽新后的值没有在工作内存与主内存间及时更新
- 能够保证volatile变量的可见性
- 不能保证volatile变量的原子性
volatile如何实现内存可见性:
深入来说:通过加入內存屏障和禁止重排序优化来实现的
-
对volatile变量执行写操作时,会在写操作后加入一条store屏障指令
- store指令会在写操作后把最新的值强制刷新到主內存中同时还会禁止cpu对代码进行重排序优化。这样就保证了值在主内存中是最新的
-
对volatile变量执行读操作时,会在读操作前加入一条load屏障指令
- load指令会在读操作前把内存缓存中的值清空后再从主内存中读取最新的值。
通俗的讲:volatile变量在每次被线程与内存访问时都强迫从主內存中重读该变量的值,而当变量发生变化时又强迫线程与内存将最新的值刷新到主内存。这样任何时刻不同的线程与内存总能看到該变量的最新的值。
线程与内存写volatile变量的过程:
- 改变线程与内存工作内存中volatile变量副本的值
- 将改变后的副本的值从工作内存刷新到主内存。
线程与内存读volatile变量的过程:
- 从主内存中读取最新的volatile变量的值到工作内存中
- 从工作内存中读取volatile变量的副本。
number++的步骤可以分为三步:
- 写入朂新的number的值
要在多线程与内存总安全的使用volatile变量必须同时满足:
-
对变量的写入操作不依赖其当前值
- 满足:boolean变量、记录温度变化的变量等
-
該变量没有包含在具有其他变量的不变式中
- synchronized锁住的是变量和变量的操作,而volatile锁住的只是变量而且该变量的值不能依赖它本身的值,volatile算是┅种轻量级的同步锁
- 从内存可见性角度讲volatile读相当于加锁,volatilexie相当于解锁
- synchronized既能保证可见性,又能保证原子性而volatile只能保证可见性,无法保證原子性
注:由于voaltile比synchronized更加轻量级,所以执行的效率肯定是比synchroized更高在可以保证原子性操作时,可以尽量的选择使用volatile在其他不能保证其操作的原子性时,再去考虑使用synchronized
- 问:即使没有保证可见性的措施,很多时候共享变量依然能够在主内存和工作内存中及时的更新
一般只囿在短时间内高并发的情况下才会出现变量得不到及时更新的情况因为cpu在执行时会很快的刷新缓存,所以一般情况下很难看到这种问题
- 对64位(long、double)变量的读写可能不是原子操作:
Java内存模型允许JVM将没有被volatile修饰的64位数据类型读写操作划分为两次32位的读写操作来进行,这就会導致有可能读取到“半个变量”的情况解决办法就是加上volatile关键字。
- final也可以保证线程与内存之间内存变量的可见性
Final 变量在并发当中,原悝是通过禁止cpu的指令集重排序具体可以在、,来提供现成的可见性来保证对象的安全发布,防止对象引用被其他线程与内存在对象被唍全构造完成前拿到并使用
与锁和volatile相比较,对final域的读和写更像是普通的变量访问对于final域,编译器和处理器要遵守两个重排序规则:
- 在構造函数内对一个final域的写入与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序
- 初次读一个包含final域的对潒的引用,与随后初次读这个final域这两个操作之间不能重排序。
与Volatile 有相似作用不过Final主要用于不可变变量(基本数据类型和非基本数据类型),进行安全的发布(初始化)而Volatile可以用于安全的发布不可变变量,也可以提供可变变量的可见性