解析java中volatile关键字

所属分类: 软件编程 / java 阅读数: 78
收藏 0 赞 0 分享

在java多线程编程中经常volatile,有时候这个关键字和synchronized 或者lock经常有人混淆,具体解析如下:
在多线程的环境中会存在成员变量可见性问题: java的每个线程都存在一个线程栈的内存空间,该内存空间保存了该线程运行时的变量信息,当线程访问某一个变量值的时候首先会根据这个变量的地址找到对象的堆内存或者是栈堆存(原生数据类型)中的具体的内容,然后把这个内同赋值一个副本保存在本线程的线程栈中,紧接着对这个变量的一切操作在线程完成退出之前都和堆栈内存中的变量内容是没有关系的,操作的是自己线程栈中的副本。当操作完后会把操作完的结果写回到主内存中。假如有两个线程A和B,同事操作某一个变量x;A对x进行了加1操作,那么B获取的副本可能是x加1后的结果,也可能是x;为了保证获取内存中最新的数据变量 需要加上 volatile 关键字,这样在每次对x进行操作的时候都会去检查下线程栈中的变量的值是不是和住内存中变量的值一样,如果不一样会重新load。
eg:

public class ThreadSee { 
//t1线程会根据flag的值做对应的操作,主线程会更改t1的值 
 public static void main(String[] args) throws InterruptedException { 
    ThReadTest th= new ThReadTest(); 
    Thread t1 = new Thread(th); 
    t1.start(); 
    Thread.sleep(1000); 
    th.changeFlag(); 
    Thread.sleep(2000); 
    System.out.println(th.getFlag()); 
  } 
 
} 
 
 
class ThReadTest implements Runnable{ 
 
  //线程访问变量时会把其load到对应的线程栈中,每次操作时都要获取内存中最新的数据 
  private volatile boolean stopflag; 
  @Override 
  public void run() { 
    int i=0; 
    while(!stopflag){ 
      i++; 
      System.out.println("=="+Thread.currentThread().getName()); 
    } 
    System.out.println("Thread finish:"+i); 
  } 
  public void changeFlag(){ 
    this.stopflag=true; 
    System.out.println(Thread.currentThread().getName()+"***********"); 
  } 
 
  public boolean getFlag(){ 
    return stopflag; 
  } 
} 

上述代码如果去掉volatile,会一直死循环执行下去。
但是volatile不能保证线程安全的同步
eg:

public class ThreadSave implements Runnable{ 
  static ThreadSave sync = new ThreadSave(); 
  static volatile int j=0; 
  //Lock lock =new ReentrantLock(); 
  public void inscane(){ 
    // lock.lock(); 
    for(int i=0;i<10000000;i++){ 
      j++; 
    } 
   //  lock.unlock(); 
  } 
  @Override 
  public void run() { 
    inscane(); 
  } 
  public static void main(String[] args) throws InterruptedException { 
    Thread t1 = new Thread(sync); 
    Thread t2 = new Thread(sync); 
    t1.start(); 
    t2.start(); 
    t1.join(); 
    t2.join(); 
    System.out.println(j); 
  } 
} 

根据上述代码执行的结果不是预期20000000,
因为对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的。
例如假如线程1,线程2 在进行线程栈与主内存read,load 操作中,发现主内存中count的值都是5,那么都会加载这个最新的值
在线程1堆count进行修改之后,会write到主内存中,主内存中的count变量就会变为6;
线程2由于已经进行read,load操作,在进行运算之后,也会更新主内存count的变量值为6;
导致两个线程及时用volatile关键字修改之后,还是会存在并发的情况。
综上所述:
volatile只会保证线程去做一个检查当前线程栈的变量值和主内存中数据值是否一样的这么一个动作,只此而已。而lock或者是synchronized 会保证某一时刻只有单个线程进入该方法,从而确保其线程安全性。
所以在如果多个线程去修改一个volatile变量那么没有实际的逻辑意义。如果一个线程去修改其他的线程依赖修改的变量值,此时是有作用的。

以上就是本文的全部内容,希望对大家的学习有所帮助。

更多精彩内容其他人还在看

Java实现一个简单的文件上传案例示例代码

这篇文章主要介绍了Java实现一个简单的文件上传案例,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

详解在spring中使用JdbcTemplate操作数据库的几种方式

这篇文章主要介绍了详解在spring中使用JdbcTemplate操作数据库的几种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

java中的switch case语句使用详解

这篇文章主要介绍了java中的switch case语句使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

深入理解Java 线程池

这篇文章主要介绍了Java 线程池的相关资料,文中讲解非常细致,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
收藏 0 赞 0 分享

详解mybatis #{}和${}的区别、传参、基本语法

这篇文章主要介绍了mybatis #{}和${}的区别、传参、基本语法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

MyBatis 中 ${}和 #{}的正确使用方法(千万不要乱用)

这篇文章主要介绍了MyBatis 中 ${}和 #{}的正确使用方法,本文给大家提到了MyBatis 中 ${}和 #{}的区别,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

详解Mybatis中的 ${} 和 #{}区别与用法

这篇文章主要介绍了Mybatis中的 ${} 和 #{}区别与用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

Maven安装过程图文详解

这篇文章主要介绍了Maven安装过程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
收藏 0 赞 0 分享

SpringBoot登录拦截配置详解(实测可用)

这篇文章主要介绍了SpringBoot登录拦截配置详解(实测可用),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

JAVA 内存溢出案例汇总

这篇文章主要介绍了JAVA 内存溢出案例的汇总,文中讲解非常细致,帮助各位工作学习时避免内存溢出,感兴趣的朋友可以了解下
收藏 0 赞 0 分享
查看更多