Java 关键字 volatile 的作用:保证线程的可见性
volatile 保证线程可见性
先贴上代码
变量 running 前面不加 volatile 修饰时:
package test;
import java.util.concurrent.TimeUnit;
public class T01_HelloVolatile {
boolean running = true;
void m() {
System.out.println("m start");
while (running) {
}
System.out.println("m end");
}
public static void main(String[] args) {
T01_HelloVolatile t = new T01_HelloVolatile();
new Thread(t::m,"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.running = false;
}
}
程序的执行结果:
我们可以发现,程序是一个死循环,永远不会结束,虽然在主线程里修改了 running 的值,但是 running 的值没有及时更新到内存,因此,t1线程无法及时拿到更新的 running 值, while (running) {} 的执行结果永远为 true,循环永远不会结束。
变量 running 的值是存在内存里面的,内存的速度约是CPU速度的1/100。
加了 volatile 后,变量 running 就变成了线程可见性的。
那么,在程序运行的过程中,变量的流程怎么样的呢?我们下面来仔细分析变量在代码执行中的流程:
如上图所示:
r : running 变量,原来是存在内存里的,但是作为CPU,在进行运算的时候,并不会每次都把 r 从内存中取出来,而是先读到本地缓存里面。下图是 CPU 的主要结构(双核CPU)
L1 L2 L3 分别是高速缓存
内存中的数据流向CPU计算单元 ALU 如图所示:
我们可以看到,CPU并不是每次都往内存中读取数据,而是先把一部分数据加载进缓存。
因此,如果在CPU中修改了变量r(running),那么就需要和主存的r进行同步,保证主存和 CPU 里的变量 r 保持数据的一致性。
有的指令可以触发马上同步,有的指令不能触发,如刚才的 while () { }
while (running) {
}
因为 running 内部没有任何东西,因此也就不包含可以触发内存同步的指令。
这里设右边的主线程,因为同一时刻,一个CPU的核只能运行一个线程,因此线程和内存之间的数据同步和CPU的缓存和内存之间的数据同步是一样的。
public static void main(String[] args) {
T01_HelloVolatile t = new T01_HelloVolatile();
new Thread(t::m,"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.running = false;
}
那么,主线程把 r (running)的值改为 false 的时候,如何让另一个线程知道呢?
主线程得做两件事情:
1)把 running = false 写回到主内存里。
2)通知另一个线程去内存里刷新变量 running 的值。
同时我们也发现,某些语句会触发内存的同步,如下面的代码段加入打印语句后:
package test;
import java.util.concurrent.TimeUnit;
public class T01_HelloVolatile {
boolean running = true;
void m() {
System.out.println("m start");
while (running) {
System.out.println("hello");
}
System.out.println("m end");
}
public static void main(String[] args) {
T01_HelloVolatile t = new T01_HelloVolatile();
new Thread(t::m,"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.running = false;
}
}
执行结果:
如上图的代码段和程序执行结果所示:在while循环里加入一条打印语句,就会在循环输出数次后,把变量 running 的值改为 false,从而结束 while 循环。
这是因为,System.out.println() 语句里本身就包含了刷新内存,做内存同步的操作。因为上面我也提到了,某些指令会刷新内存。
但是,不能依赖这种东西来进行同步内存,保持数据的一致性。假如业务逻辑里面没有包含刷新内存的指令,就不会进行同步。
ps:上述提到的刷新内存的指令,并不是指具体的哪一条 Java 语句,而是 CPU汇编码的底层指令。
从 Java代码 到 CPU汇编码 的过程是:
Java代码 -> Java汇编码(xxx.class文件)-> CPU汇编码
volatile boolean running = true;
因此,我们可以在变量 running 之前加关键字 volatile,因为 volatile 的一个特点就是 volatile 修饰的内存,都是线程可见的。
volatil 关键字保证了变量 running 在线程之间的可见性。
经 volatile 修改的完整代码如下:
package test;
import java.util.concurrent.TimeUnit;
public class T01_HelloVolatile {
volatile boolean running = true;
void m() {
System.out.println("m start");
while (running) {
}
System.out.println("m end");
}
public static void main(String[] args) {
T01_HelloVolatile t = new T01_HelloVolatile();
new Thread(t::m,"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.running = false;
}
}
程序的执行结果为:
根据代码可以看出,经过了 volatile 关键字修饰的变量 running,变成了线程可见的。
在主线程 main 中修改了变量 running ,置为 false。线程 t1 也会及时从内存中刷新变量 running 的值。
从而,while (running) {} 循环在 running 置为 false 的时候,循环就会结束,也就不会陷入一开始由于线程不可见而造成的程序死循环。
线程可见性的小结:
多线程为了提高效率,在本地缓存数据,造成数据修改不可见
要想保证可见,要么触发同步指令,要么加上 volatile。
被volatile修饰的内存,只要有修改,马上同步到涉及到的每个线程。
本文地址:https://blog.csdn.net/a451343010/article/details/109613286
推荐阅读
-
Java 关键字 volatile 的作用:保证线程的可见性
-
浅谈 Java 中的 volatile 作用(一):线程可见性
-
Java中关于volatile不能保证线程安全的实例
-
java中volatile不能保证线程安全问题的实例讲解
-
java多线程关键字volatile、lock以及synchronized的简单介绍
-
Java中关于volatile不能保证线程安全的实例
-
Java 关键字 volatile 的作用:保证线程的可见性
-
java中volatile不能保证线程安全问题的实例讲解
-
一个具体的例子学习Java volatile关键字 Javavolatile编程多线程编程并发
-
Java中关键字volatile 和 synchronized 的作用和区别