Java中volatile关键字的线程的可见性、有序性详解
引言
在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、有序性
在多线程环境下,由于编译器和处理器对指令进行优化和重排序,可能会导致操作的执行顺序发生改变,从而违反了代码编写的原始顺序。
为了保证有序性,可以使用以下方法:
使用volatile关键字:对关键的共享变量使用volatile关键字进行声明,以确保对该变量的写操作立即对其他线程可见。
使用synchronized关键字或Lock机制:通过使用同步块或锁来保护共享资源的读写操作,确保线程之间的有序访问。
使用原子类:Java提供了一些原子类(如AtomicInteger、AtomicLong等)来进行原子操作,这些原子类的方法能够保证操作的原子性、可见性和有序性。
简单来说volatile避免了指令重排,也就避免了多线程中可能产生的问题。
到此这篇关于Java中volatile关键字的线程的可见性、有序性详解的文章就介绍到这了,更多相关volatile的线程的可见性、有序性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
java.lang.NullPointerException异常问题解决方案
这篇文章主要介绍了java.lang.NullPointerException异常问题解决方案,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下2021-08-08Mybatis CachingExecutor二级缓存使用示例详解
这篇文章主要介绍了 Mybatis的CachingExecutor与二级缓存使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-09-09
最新评论