欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java 关键字 volatile 的作用:保证线程的可见性

程序员文章站 2022-04-15 19:03:43
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) {...

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;

    }

}

程序的执行结果:

Java 关键字 volatile 的作用:保证线程的可见性

我们可以发现,程序是一个死循环,永远不会结束,虽然在主线程里修改了 running 的值,但是 running 的值没有及时更新到内存,因此,t1线程无法及时拿到更新的 running 值, while (running) {} 的执行结果永远为 true,循环永远不会结束。

变量 running 的值是存在内存里面的,内存的速度约是CPU速度的1/100。

加了 volatile 后,变量 running 就变成了线程可见性的。

Java 关键字 volatile 的作用:保证线程的可见性

那么,在程序运行的过程中,变量的流程怎么样的呢?我们下面来仔细分析变量在代码执行中的流程:

Java 关键字 volatile 的作用:保证线程的可见性

如上图所示:

r : running 变量,原来是存在内存里的,但是作为CPU,在进行运算的时候,并不会每次都把 r 从内存中取出来,而是先读到本地缓存里面。下图是 CPU 的主要结构(双核CPU)

Java 关键字 volatile 的作用:保证线程的可见性

L1 L2 L3 分别是高速缓存

内存中的数据流向CPU计算单元 ALU 如图所示:

Java 关键字 volatile 的作用:保证线程的可见性

我们可以看到,CPU并不是每次都往内存中读取数据,而是先把一部分数据加载进缓存。

Java 关键字 volatile 的作用:保证线程的可见性

因此,如果在CPU中修改了变量r(running),那么就需要和主存的r进行同步,保证主存和 CPU 里的变量 r 保持数据的一致性

有的指令可以触发马上同步,有的指令不能触发,如刚才的 while () { }

while (running) {

}

 因为 running 内部没有任何东西,因此也就不包含可以触发内存同步的指令。

Java 关键字 volatile 的作用:保证线程的可见性

这里设右边的主线程,因为同一时刻,一个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;
        
    }

}

执行结果:

Java 关键字 volatile 的作用:保证线程的可见性

如上图的代码段和程序执行结果所示:在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;

    }

}

程序的执行结果为:

Java 关键字 volatile 的作用:保证线程的可见性

根据代码可以看出,经过了 volatile 关键字修饰的变量 running,变成了线程可见的

在主线程 main 中修改了变量 running ,置为 false。线程 t1 也会及时从内存中刷新变量 running 的值。

从而,while (running) {} 循环在 running 置为 false 的时候,循环就会结束,也就不会陷入一开始由于线程不可见而造成的程序死循环

 

线程可见性的小结:

多线程为了提高效率,在本地缓存数据,造成数据修改不可见

要想保证可见,要么触发同步指令,要么加上 volatile。

被volatile修饰的内存,只要有修改,马上同步到涉及到的每个线程。

本文地址:https://blog.csdn.net/a451343010/article/details/109613286