理解java内存可见性,首先我们要理解java内存模型。什么是java内存模型(JMM)?
Java Memory Model (JAVA 内存模型)描述线程之间如何通过内存(memory)来进行交互。 具体说来, JVM中存在一个主存区(Main Memory或Java Heap Memory),对于所有线程进行共享,而每个线程又有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生
在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。
java内存模型如下图所示:
共享变量可见性实现原理图下图所示:
主要有一下两个步骤:
1,把工作内存1中更新过的共享变量刷新到主内存中
2,将主内存中最新的共享变量的值更新到工作内存中
两条规定:
1,线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写
2,不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量的值的传递需要通过主内存来完成。
导致共享变量在线程间不可见的原因:
1,线程的交叉执行 -->原子性
2,重排序接合线程交叉执行-->原子性
3,共享变量未及时更新 -->可见性
我们先了解下重排序:
重排序是指:代码书写的顺序与实际执行的顺序不一致,指令重排序是编译器或处理器为了提高程序性能而做的优化
1,编译器优化的重排序(编译器优化)
2,指令级并行重排序(处理器优化)(多核)
3,内存系统的重排序(处理器优化)
如下图:
as-if-serial是使重排序的结果和代码的书写顺序一致:重排序不会给单线程带来内存可见性问题。多线程中程序交错执行时,重排序可能会造成内存可见性问题。
synchronzied实现可见性
synchronzied能够实现:
原子性(同步)、可见性
JMM关于synchronzied的两条规定:
线程解锁前,必须把共享变量的最新值更新到主内存中
线程加锁时,将清除工作内存中的共享变量值,从而使共享变量时需要从主内存中重新读取最新的值(注意加锁和解锁需要同一把锁)
线程解锁前对共享变量的修改在下次加锁时对其他线程可见。
线程执行互斥代码的过程:
1.获得互斥锁
2.清空工作内存
3.从主内存拷贝变量的最新副本到工作内存
4.执行代码
5.将更改后的共享变量的值刷新到主内存中
6.释放互斥锁
volatile实现可见性
volatail关键字能够保证volatile变量的可见性,但是不能保证volatile变量复合操作的原子性
volatile如何实现内存可见性:
通过加入内存屏障和禁止重排序优化来实现的。
1,对volatile变量执行写操作时,会在写操作后加入一条store屏障指令
2,对volatile变量执行读操作时,会在读操作前加入一条load屏障指令
也就是说,volatile变量在每次被线程访问,都强迫从主内存中读取改变量的值,而当该变量的值发生变化时,又会强迫线程将最新的值刷新到主>内存中。这样任何时刻,不同的线程总能看到改变量的最新值。
线程写volatile变量的过程:
1,改变线程工作内存中volatile变量副本的值
2,将改变后的副本的值从工作内存刷新到主内存中
线程读volatile变量的过程:
1,从主内存中读取volatile变量的罪行值到线程的工作内存中
2,从工作内存中读取volatile变量的副本
volatile部保证volatile变量复合操作的原子性:
例如:
private int number=0;
number++;不是原子操作
1,读取number的值
2,将number的值加1
3,写入最新的number的值
如果synchronzied(this){
number++;
}
可以变为原子操作
但是private volatile int number=0;
不可以变为原子操作
Java Memory Model (JAVA 内存模型)描述线程之间如何通过内存(memory)来进行交互。 具体说来, JVM中存在一个主存区(Main Memory或Java Heap Memory),对于所有线程进行共享,而每个线程又有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生
在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。
java内存模型如下图所示:
共享变量可见性实现原理图下图所示:
主要有一下两个步骤:
1,把工作内存1中更新过的共享变量刷新到主内存中
2,将主内存中最新的共享变量的值更新到工作内存中
两条规定:
1,线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写
2,不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量的值的传递需要通过主内存来完成。
导致共享变量在线程间不可见的原因:
1,线程的交叉执行 -->原子性
2,重排序接合线程交叉执行-->原子性
3,共享变量未及时更新 -->可见性
我们先了解下重排序:
重排序是指:代码书写的顺序与实际执行的顺序不一致,指令重排序是编译器或处理器为了提高程序性能而做的优化
1,编译器优化的重排序(编译器优化)
2,指令级并行重排序(处理器优化)(多核)
3,内存系统的重排序(处理器优化)
如下图:
as-if-serial是使重排序的结果和代码的书写顺序一致:重排序不会给单线程带来内存可见性问题。多线程中程序交错执行时,重排序可能会造成内存可见性问题。
synchronzied实现可见性
synchronzied能够实现:
原子性(同步)、可见性
JMM关于synchronzied的两条规定:
线程解锁前,必须把共享变量的最新值更新到主内存中
线程加锁时,将清除工作内存中的共享变量值,从而使共享变量时需要从主内存中重新读取最新的值(注意加锁和解锁需要同一把锁)
线程解锁前对共享变量的修改在下次加锁时对其他线程可见。
线程执行互斥代码的过程:
1.获得互斥锁
2.清空工作内存
3.从主内存拷贝变量的最新副本到工作内存
4.执行代码
5.将更改后的共享变量的值刷新到主内存中
6.释放互斥锁
volatile实现可见性
volatail关键字能够保证volatile变量的可见性,但是不能保证volatile变量复合操作的原子性
volatile如何实现内存可见性:
通过加入内存屏障和禁止重排序优化来实现的。
1,对volatile变量执行写操作时,会在写操作后加入一条store屏障指令
2,对volatile变量执行读操作时,会在读操作前加入一条load屏障指令
也就是说,volatile变量在每次被线程访问,都强迫从主内存中读取改变量的值,而当该变量的值发生变化时,又会强迫线程将最新的值刷新到主>内存中。这样任何时刻,不同的线程总能看到改变量的最新值。
线程写volatile变量的过程:
1,改变线程工作内存中volatile变量副本的值
2,将改变后的副本的值从工作内存刷新到主内存中
线程读volatile变量的过程:
1,从主内存中读取volatile变量的罪行值到线程的工作内存中
2,从工作内存中读取volatile变量的副本
volatile部保证volatile变量复合操作的原子性:
例如:
private int number=0;
number++;不是原子操作
1,读取number的值
2,将number的值加1
3,写入最新的number的值
如果synchronzied(this){
number++;
}
可以变为原子操作
但是private volatile int number=0;
不可以变为原子操作