Code前端首页关于Code前端联系我们

Java关键字使用volatile原理及示例代码详解

terry 2年前 (2023-09-25) 阅读数 51 #后端开发

volatile关键字常用于修改变量。然而,volatile本人却很容易被利用。本文将介绍volatile的原理和使用。

在介绍volatile关键字原理之前,我们首先要了解JVM运行时的内存分配逻辑。

Java关键字volatile原理使用方式及示例代码详解

成员变量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前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门