Java关键字使用volatile原理及示例代码详解
volatile关键字常用于修改变量。然而,volatile本人却很容易被利用。本文将介绍volatile的原理和使用。
在介绍volatile关键字原理之前,我们首先要了解JVM运行时的内存分配逻辑。
成员变量i存储在堆内存中。每个线程在启动时都会有自己的线程堆栈。如果一个线程想要访问i类的成员变量,它通过引用10获取堆上i变量的实际值,然后将该变量值复制到自己的堆栈内存中。 ,作为变量的副本,那么线程将不再实际接触堆上的变量。每个线程都有自己的本地副本,彼此隔离。线程访问栈自身内存的效率比访问堆时要高。当线程修改变量 i 的值时,它仅修改自己的线程副本中的值。修改完成后,线程自己的副本中的值会在线程退出之前刷新到堆上。
保证内存可见性
对于下面的代码:
public class VolatileTest implements Runnable{ //volatile private static boolean flag = false; @Override public void run() { while (!flag){ System.out.println(Thread.currentThread().getName() +"执行中"); } System.out.println(Thread.currentThread().getName() +"执行完毕"); } //main线程 public static void main(String[] args) throws InterruptedException { new Thread(new VolatileTest(), "支线程Volatile").start(); Thread.sleep(1000); flag = true; } }
大多数情况下可以正常中断,但是一旦抛出异常,线程就会无限循环。因此,您需要将关键字 volatile 添加到标志中。对于添加了volatile关键字的变量值,如果线程1修改了这个值,修改后的值将被强制直接写入堆内存。其他线程各自线程堆栈中的变量副本无效,只能从堆中加载最新的变量。价值。保证跨多个线程的内存可见性。
值得注意的是,volatile的关键字并不保证是原子的。
private volatile int i; i++;
i ++ 该操作包括三个部分:取值、自增、赋值。它不能直接完成。上述试图利用volatile来实现原子性的做法是错误的。
禁止重新排列指令
现代JVM对代码执行顺序进行了一些优化。例如:
int a = 4; int b = 5; int c = a + b;
JVM对上述三个指令进行优化后,执行时间顺序不一定是从上到下。它可以是第二--->第一-->第三。总之,不会影响最终的实施结果。
但是在多线程的情况下,下面的代码会有风险:
//线程1: context = loadContext(); inited = true; //线程2: while(!inited ){ } doSomething(context);
线程1的两条语句之间没有依赖关系,重新排列指令后,有可能inited设置为true后,上下文是未初始化。 。线程2发现inited为true,认为初始化完成,退出循环,并使用尚未初始化的上下文执行doSomething()方法。报告错误。所以我们可以使用volatile关键字来修改inited来保证上下文被初始化。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。