可见性:一个线程对共享变量值的修改,能够及时的被其它线程看到。
共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。
在Java内存模型(Java Memory Model)中描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节。
所有的变量都存储在主内存中每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)在这里,有2条规定: - 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主存中读写 - 不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成
线程1对共享变量的修改,要想被线程2及时看到,必须要经过如下两个步骤:
把工作内存1中更新过的共享变量刷新到主存中将主内存中最新的共享变量的值更新到工作内存2中步骤如下图所示: 1. 初始状态下,主内存中和所有线程的工作内存中的共享变量X的值都是0
2. 线程1更改工作线程1中X的副本值为1
3. 工作内存1将副本X的值刷新到主内存中
4. 主内存将最新的X的值1更新到工作内存2中
通过以上步骤,就保证了共享变量在不同线程的工作内存中的可见性。
线程解锁前对共享变量的修改在下次加锁时对其它线程可见
重排序:代码书写的顺序与实际执行的顺序不同,指令重排序是编译器或处理器为了提高程序性能而做的优化
编译器优化的重排序(编译器优化)指令集并行重排序(处理器优化)内存系统的重排序(处理器优化)代码顺序是
int number = 1;int result = 0;重排序后,执行顺序可能是
int result = 0;int number = 1;as-if-serial:无论如何重排序,程序执行的结果应该与代码顺序执行的结果一致(Java编译器、运行时和处理器都会保证Java在单线程下遵循as-if-serial语义)
int num1= 1; //语句1int num2= 2; //语句2int num3 = num1 + num2; //语句3单线程:语句1、2的顺序可以重排,但语句3不能 重排序不会给单线程带来内存可见性的问题 多线程中程序交错执行时,重排序可能造成内存可见性问题
导致共享变量在线程间不可见的原因 | synchronized解决办法 |
---|---|
线程的交叉执行 | 原子性 |
重排序结合线程交叉执行 | 原子性 |
共享变量更新后的值没有在工作内存与主内存之间及时更新 | 可见性 |
volatile关键字: - 能够保证volatile变量的可见性 - 不能保证volatile变量符合操作的原子性
深入来说:通过加入内存屏障和禁止重排序优化来实现 - 对volatile变量执行写操作时,会在写操作后加入一条store屏障指令 - 对volatile变量执行读操作时,会在写操作后加入一条load屏障指令
通俗的讲:volatile变量在每次被线程访问时,都强迫从主内存中读取该变量的值,而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存中。这样任何时刻,不同的线程总能看到该变量的最新值
number++ 包含如下三个步骤: 1. 读取number的值 2. 将number的值加1 3. 写入最新的number值
加入synchronized,变为原子操作
synchronized(this) { number++}volatile int number = 0; //变为volatile变量,无法保证原子性要在多线程中安全地使用volatile变量,必须同时满足: 1. 对变量的写入操作不依赖于其当前值 - 如:number++、count = count * 5等 2. 该变量没有包含在具有其他变量的不变式中 - 如:不变式low
新闻热点
疑难解答