volatile可见性的理解

在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。它在某些情况下比synchronized的开销更小。

Volatile的官方定义

Java语言规范第三版中对volatile的定义如下: java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。

为什么要使用Volatile

Volatile变量修饰符如果使用恰当的话,它比synchronized的使用和执行成本会更低,因为它不会引起线程上下文的切换和调度。

内存可见性

由于 Java 内存模型( JMM)规定,所有的变量都存放在主内存中,而每个线程都有着自己的工作内存(高速缓存)。

线程在工作时,需要将主内存中的数据拷贝到工作内存中。这样对数据的任何操作都是基于工作内存(效率提高),并且不能直接操作主内存以及其他线程工作内存中的数据,之后再将更新之后的数据刷新到主内存中。

这里所提到的主内存可以简单认为是堆内存,而工作内存则可以认为是栈内存

如下图所示:

image

所以在并发运行时可能会出现线程 B 所读取到的数据是线程 A 更新之前的数据。

显然这肯定是会出问题的,因此 volatile 的作用出现了:

当一个变量被 volatile 修饰时,任何线程对它的写操作都会立即刷新到主内存中,并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。

volatile 修饰之后并不是让线程直接从主内存中获取数据,依然需要将变量拷贝到工作内存中。

本文主要针对 共享变量 可见性理解的演示。

代码片段1

public class T01_HelloVolatile {
    /*volatile*/ boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别

    void m() {
        System.out.println("m  修改【running】值前进行启动 ,【running】的值为" + running);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        while (running) {

        }
        System.out.println("m end!");
    }

    void n() {
        System.out.println("n 进行【running】的修改,修改前 【running】值为" + running);
        running = false;
        System.out.println("n 进行【running】的修改,修改后 【running】值为" + running);
    }

    void f() {
        System.out.println("f  修改【running】值前进行启动 ,【running】的值为" + running);

        while (running) {
        }
        System.out.println("f end ,【running】的值为" + running);
    }

    public static void main(String[] args) throws InterruptedException {
        T01_HelloVolatile t = new T01_HelloVolatile();

        new Thread(t::m, "t1").start();

        TimeUnit.SECONDS.sleep(5);
        new Thread(t::n, "t2").start();

        TimeUnit.SECONDS.sleep(1);
        new Thread(t::f, "t3").start();
    }

}
```
此段代码演示了 volatile .
- 先启动线程 m,m一直持有 running变量。
- 在启动线程n,n线程进行running属性的更改。
- 在启动线程f ,在f线程里面读取 running变量,验证是否可以拿到running修改后的变量。

以上代码输出结果:
 
m  修改【running】值前进行启动 ,【running】的值为true
n 进行【running】的修改,修改前 【running】值为true
n 进行【running】的修改,修改后 【running】值为false
f  修改【running】值前进行启动 ,【running】的值为false
f end ,【running】的值为false  

结果显示 m 线程未结束,f线程结束
 
以上说明, 线程n修改了running值之后,之后的f线程来进行访问这个值的时候是能访问到修改的内容的。在线程n修改这个值之前启动的线程m是没法访问到线程n修改的这个值的内容的。

代码片段2

public class T01_HelloVolatile {
    /*volatile*/ boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别

    void m() {
        System.out.println("m  修改【running】值前进行启动 ,【running】的值为" + running);
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        while (running) {
            // 此处千万不要 使用 System.out.println 此句代码会触发同步机制

        }
        System.out.println("m end!");
    }

    void n() {
        System.out.println("n 进行【running】的修改,修改前 【running】值为" + running);
        running = false;
        System.out.println("n 进行【running】的修改,修改后 【running】值为" + running);
    }

    void f() {
        System.out.println("f  修改【running】值前进行启动 ,【running】的值为" + running);

        while (running) {
        }
        System.out.println("f end ,【running】的值为" + running);
    }

    public static void main(String[] args) throws InterruptedException {
        T01_HelloVolatile t = new T01_HelloVolatile();

        new Thread(t::m, "t1").start();

        TimeUnit.SECONDS.sleep(1);
        new Thread(t::n, "t2").start();

        TimeUnit.SECONDS.sleep(1);
        new Thread(t::f, "t3").start();
    }

}
 

此段代码比 代码片段1 中 m线程多了一段睡眠时间,加上 5 秒的睡眠时间之后,执行结果如下:
 
m  修改【running】值前进行启动 ,【running】的值为true
n 进行【running】的修改,修改前 【running】值为true
n 进行【running】的修改,修改后 【running】值为false
f  修改【running】值前进行启动 ,【running】的值为false
f end ,【running】的值为false
m end!

m 线程正常结束。 那么 片段1 中的总结 就不正确,应该正确理解为 一直持有的这个变量 通过volatile 修饰后,当其他线程修改这个变量之后,这个变量在当前线程就可见

代码片段3

public class T01_HelloVolatile {
    volatile boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别

    void m() {
        System.out.println("m  修改【running】值前进行启动 ,【running】的值为" + running);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        while (running) {
            // 此处千万不要 使用 System.out.println 此句代码会触发同步机制

        }
        System.out.println("m end!");
    }

    void n() {
        System.out.println("n 进行【running】的修改,修改前 【running】值为" + running);
        running = false;
        System.out.println("n 进行【running】的修改,修改后 【running】值为" + running);
    }

    void f() {
        System.out.println("f  修改【running】值前进行启动 ,【running】的值为" + running);

        while (running) {
        }
        System.out.println("f end ,【running】的值为" + running);
    }

    public static void main(String[] args) throws InterruptedException {
        T01_HelloVolatile t = new T01_HelloVolatile();

        new Thread(t::m, "t1").start();

        TimeUnit.SECONDS.sleep(5);
        new Thread(t::n, "t2").start();

        TimeUnit.SECONDS.sleep(1);
        new Thread(t::f, "t3").start();
    }
}
此段代码和代码片段1 做比较因为多了 volatile 关键字,所以 m f 线程都能正常结束,和代码片段1 相比
来说明来 volatile 修饰的变量是可见的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值