详解volatile关键字、线程的可见性、有序性

0、引言

        在juc多线程并发编程中,常常需要关注线程的“可见性”与“有序性”。本文将详细介绍这两部分内容,以及volatile关键字的使用。
        阅读本文前需要一些jvm运行时内存、进程与线程、共享内存、锁等相关知识。

1、可见性

1.1 定义

定义:

        当一个对象在多个内存中都存在副本时,如果一个内存修改了共享变量,其它线程也应该能够看到被修改后的值。

1.2 解释

         解释这句话,首先我们要知道,进程是操作系统分配资源的最小单位,在每个进程里,都包含有数据共享的区域(例如其中有成员变量、堆、静态常量等,详情查阅jvm运行时内存),并可能包含多个线程。
        在jvm底层,每个线程想要操作某个共享变量时,不能够直接操作共享区域的“主存”,而是要先把数据从主存读到线程自己的高速缓存中,再进行操作,再赋值回去。

        “具体来说,当一个线程要读取某个变量的值时,它首先检查自己的工作缓存中是否存在该变量的副本。
        如果存在,则直接读取副本的值;
        如果不存在,则从主内存中获取该变量的最新值,并将其复制到自己的工作缓存中。
        类似地,当一个线程要修改某个变量的值时,它首先会在自己的工作缓存中修改副本,再根据一定的策略将变化同步回主内存。”

         那么可见性的概念相应而来:多个线程都存了同一个对象副本,如果此时有一个内存修改了共享变量,其他的线程应该能够“看到”,而不是傻b一样仍旧拿着自己缓存里的值操作了。

1.3 实例说明

        例如下面情况,如果main 线程对 run 变量的修改对于 t 线程不可见,导致了 t 线程无法停止:

         其原因是t线程频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中, 减少对主存中 run 的访问。
        那么,当主线程(也就是其他线程)把run变为false,由于子线程t(当前线程)仍然从高速缓存里读的run,会认为run并没有改变,从而逻辑出现问题。

1.4 解决方法:volatile(易变关键字)

         作用:volatile可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值。(关键点)

        然而 volatile 并不能保证原子性,例如不加锁的多线程执行函数,对成员变量i进行i++ i--,最后得到的结果i不一定是不变的。
        因为volatile 关键字,只能让该线程在使用数据前强制从主存读取,但读取后是否发生改变是看不见的。如果有别的线程在该线程读取后成功改变了共享内存的值,还是会发生指令交错。

2、有序性

        在多线程环境下,由于编译器和处理器对指令进行优化和重排序,可能会导致操作的执行顺序发生改变,从而违反了代码编写的原始顺序。

为了保证有序性,可以使用以下方法:

  1. 使用volatile关键字:对关键的共享变量使用volatile关键字进行声明,以确保对该变量的写操作立即对其他线程可见。

  2. 使用synchronized关键字或Lock机制:通过使用同步块或锁来保护共享资源的读写操作,确保线程之间的有序访问。

  3. 使用原子类:Java提供了一些原子类(如AtomicInteger、AtomicLong等)来进行原子操作,这些原子类的方法能够保证操作的原子性、可见性和有序性。

         简单来说volatile避免了指令重排,也就避免了多线程中可能产生的问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

好奇的7号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值