对volatile的理解
保证可见性、不保证原子性、禁止指令重排
1. 保证可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值
当不添加volatile关键字时示例:
```java package com.jian8.juc; import java.util.concurrent.TimeUnit; /** * 1验证volatile的可见性 * 1.1 如果int num = 0,number变量没有添加volatile关键字修饰 * 1.2 添加了volatile,可以解决可见性 */ public class VolatileDemo { public static void main(String[] args) { visibilityByVolatile();//验证volatile的可见性 } /** * volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改 */ public static void visibilityByVolatile() { MyData myData = new MyData(); //第一个线程 new Thread(() -> { System.out.println(Thread.currentThread().getName() + "\t come in"); try { //线程暂停3s TimeUnit.SECONDS.sleep(3); myData.addToSixty(); System.out.println(Thread.currentThread().getName() + "\t update value:" + myData.num); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }, "thread1").start(); //第二个线程是main线程 while (myData.num == 0) { //如果myData的num一直为零,main线程一直在这里循环 } System.out.println(Thread.currentThread().getName() + "\t mission is over, num value is " + myData.num); } } class MyData { // int num = 0; volatile int num = 0; public void addToSixty() { this.num = 60; } } ```
输出结果:
```java
thread1 come in
thread1 update value:60
//线程进入死循环
```
当我们加上`volatile`关键字后,`volatile int num = 0;`输出结果为:
```java
thread1 come in
thread1 update value:60
main mission is over, num value is 60
//程序没有死循环,结束执行
2. ==不保证原子性==
原子性:不可分割、完整性,即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么同时失败
验证示例(变量添加volatile关键字,方法不添加synchronized):
```java package com.jian8.juc; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * 1验证volatile的可见性 * 1.1 如果int num = 0,number变量没有添加volatile关键字修饰 * 1.2 添加了volatile,可以解决可见性 * * 2.验证volatile不保证原子性 * 2.1 原子性指的是什么 * 不可分割、完整性,即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么同时失败 */ public class VolatileDemo { public static void main(String[] args) { // visibilityByVolatile();//验证volatile的可见性 atomicByVolatile();//验证volatile不保证原子性 } /** * volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改 */ //public static void visibilityByVolatile(){} /** * volatile不保证原子性 * 以及使用Atomic保证原子性 */ public static void atomicByVolatile(){ MyData myData = new MyData(); for(int i = 1; i <= 20; i++){ new Thread(() ->{ for(int j = 1; j <= 1000; j++){ myData.addSelf(); myData.atomicAddSelf(); } },"Thread "+i).start(); } //等待上面的线程都计算完成后,再用main线程取得最终结果值 try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } while (Thread.activeCount()>2){ Thread.yield(); } System.out.println(Thread.currentThread().getName()+"\t finally num value is "+myData.num); System.out.println(Thread.currentThread().getName()+"\t finally atomicnum value is "+myData.atomicInteger); } } class MyData { // int num = 0; volatile int num = 0; public void addToSixty() { this.num = 60; } public void addSelf(){ num++; } AtomicInteger atomicInteger = new AtomicInteger(); public void atomicAddSelf(){ atomicInteger.getAndIncrement(); } } ``` 执行三次结果为: ```java //1. main finally num value is 19580 main finally atomicnum value is 20000 //2. main finally num value is 19999 main finally atomicnum value is 20000 //3. main finally num value is 18375 main finally atomicnum value is 20000 //num并没有达到20000
3. 禁止指令重排
有序性:在计算机执行程序时,为了提高性能,编译器和处理器常常会对**==指令做重拍==**,一般分以下三种
```mermaid
graph LR
源代码 --> id1["编译器优化的重排"]
id1 --> id2[指令并行的重排]
id2 --> id3[内存系统的重排]
id3 --> 最终执行的指令
style id1 fill:#ff8000;
style id2 fill:#fab400;
style id3 fill:#ffd557;
```
单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。
处理器在进行重排顺序是必须要考虑指令之间的**==数据依赖性==**
==多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性时无法确定的,结果无法预测==
重排代码实例:
声明变量:`int a,b,x,y=0`
| 线程1 | 线程2 |
| -------------- | -------------- |
| x = a; | y = b; |
| b = 1; | a = 2; |
| 结 果 | x = 0 y=0 |
如果编译器对这段程序代码执行重排优化后,可能出现如下情况:
| 线程1 | 线程2 |
| -------------- | -------------- |
| b = 1; | a = 2; |
| x= a; | y = b; |
| 结 果 | x = 2 y=1 |
这个结果说明在多线程环境下,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的
volatile实现禁止指令重排,从而避免了多线程环境下程序出现乱序执行的现象
**==内存屏障==**(Memory Barrier)又称内存栅栏,是一个CPU指令,他的作用有两个:
1. 保证特定操作的执行顺序
2. 保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)
由于编译器和处理器都能执行指令重排优化。如果在之零件插入一i奥Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排顺序,也就是说==通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化==。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。
```mermaid
graph TB
subgraph
bbbb["对Volatile变量进行读操作时,<br>回在读操作之前加入一条load屏障指令,<br>从内存中读取共享变量"]
ids6[Volatile]-->red3[LoadLoad屏障]
red3-->id7["禁止下边所有普通读操作<br>和上面的volatile读重排序"]
red3-->red4[LoadStore屏障]
red4-->id9["禁止下边所有普通写操作<br>和上面的volatile读重排序"]
red4-->id8[普通读]
id8-->普通写
end
subgraph
aaaa["对Volatile变量进行写操作时,<br>回在写操作后加入一条store屏障指令,<br>将工作内存中的共享变量值刷新回到主内存"]
id1[普通读]-->id2[普通写]
id2-->red1[StoreStore屏障]
red1-->id3["禁止上面的普通写和<br>下面的volatile写重排序"]
red1-->id4["Volatile写"]
id4-->red2[StoreLoad屏障]
red2-->id5["防止上面的volatile写和<br>下面可能有的volatile读写重排序"]
end
style red1 fill:#ff0000;
style red2 fill:#ff0000;
style red4 fill:#ff0000;
style red3 fill:#ff0000;
style aaaa fill:#ffff00;
style bbbb fill:#ffff00;

浙公网安备 33010602011771号