`
frank-liu
  • 浏览: 1666186 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java volatile关键字的理解

 
阅读更多

一个多线程的示例引发的问题

在讨论这个关键字之前先看一个多线程的示例代码:

 

public class RaceCondition {            
  private static boolean done;
  
  public static void main(final String[] args) throws InterruptedException{
    new Thread(
      new Runnable() {
        public void run() {
  	  int i = 0;
          while(!done) { i++; }
            System.out.println("Done!");
          }
        }
    ).start();

    System.out.println("OS: " + System.getProperty("os.name"));  
    Thread.sleep(2000);
    done = true;
    System.out.println("flag done set to true");
  }
}

 

这部分代码主要是设置了一个static变量done。main函数的主线程会打印一些必要的信息之后修改该变量的值。而另外一个派生的线程则一直在读取done的信息,根据信息来判断下一步的行为。总的来说就是一个线程等另一个线程修改的数值结果。

如果运行这一段代码,会是什么结果呢?

下面是在我的具体执行环境下的情况:

 

OS: Linux
flag done set to true

比较有意思的就是代码执行到这里的时候并没有完全退出来,只是一直停在这里。 

从代码的字面含义来看,当main函数主线程将done设置为true的时候,派生的线程应该读取到这个值然后跳出循环的啊,为什么没有跳出来呢?

先别急,如果我们换一种方式来执行上面的代码试试,就会发现不一样的结果了:

如果我们输入如下的命令:

 

java -d32 RaceCondition

这次执行的结果会是:

 

OS: Linux
flag done set to true
Done!

这么看来,实在是太诡异了。到底是怎么回事呢?

 

第一步分析

        实际上,首先这个问题就在于我们执行代码的时候所采用的执行方式。java的命令执行模式是和平台相关的。当我们在linux平台用java RaceCondition的时候,java默认采用的是server模式。而后面用java -d32 RaceCondition,就是手动的指示采用client模式来执行。这么说来问题就出在执行模式的差别。

        确实,server模式和client模式执行java代码会有一些差别。server模式会jit的时候对代码做一些优化。更进一步来说,我们前面的问题就在于server模式的优化。为什么这么一优化之后结果就不对了呢?我们可以看下面jvm的结构图来做下一步分析。

jvm

        上面图中,每个java线程都有一套自己独立的栈、指令寄存器、缓存等线程本地存储空间。这样,每次线程执行的时候,一些线程本地的变量或者传入的参数可以在线程内部存储空间处理。而这个问题的关键也在于线程的本地存储空间。在对前面的代码进行优化之后,线程读取到done变量会读取一个副本到本地的存储空间。这样以后每次线程访问这个变量的时候,不会跑到原来定义该变量的内存中来读取,而是直接读取自身的那个副本。这样,我们才会看到第一种方式的执行不会结束。而前面我们在client模式下看到的结果是因为没有这些优化,每次还是从done变量的内存中来读取。

        那么,如果要解决上面那个问题,有哪些办法呢?

一种选项,volatile

        如果说为了结果这样一个问题,我们可以有好几种选项,比如说将done声明为原子数据类型,或者采用synchronized方式来访问它。我们这里可以考虑一下volatile这种方式。

        volatile表示它告诉jit编译器,不要对所修饰的变量进行任何优化。这样,每次每个线程访问修饰的变量时,每次都是访问内存中这个独一无二的变量,不会有其他的本地拷贝。

        volatile提供唯一的内存访问地址容易让人产生一些误解。觉得volatile变量看起来可以实现多线程的安全访问。实际未必。

volatile不保证多个线程访问的原子性

        比如说我们有多个线程要访问一个网站的计数器,假设该变量为count。那么每个对该变量进行一次递增的代码是count++;粗粗看来用volatile应该可以满足了。实际上会有问题。

        我们对count递增的操作实际的执行细节里是细分成了三个步骤。1.读取count,2.递增count 3.将修改后的数值写会内存。 问题就在于,当有多个线程访问的时候,会出现竞争条件,可能导致数据错误。

 

volatile也不能保证线程的互斥访问

       和synchronized的关键字不一样,volatile对于访问变量没有严格限制。所以可以同时有多个线程进行读写操作。这样就不能保证线程安全的。

 

性能方面

        既然volatile修饰的变量就是放在内存中,所以每次每个线程访问的时候都要来访问内存。这样和直接访问寄存器或者缓存比起来要慢不少。如果有大量的线程要访问某些变量,都要去访问内存的话。会带来性能方面的影响。在实际的计算机体系结构中,对于volatile变量的读取性能已经和非volatile变量的读取非常接近,几乎可以忽略了。只有对volatile的写操作会相对慢一些。

 

volatile一些应用的场景

        看了前面的分析,让人觉得有点沮丧。似乎这东西没什么用。从前面对性能的分析,我们可以看到一个应用。那就是如果只有一个线程进行数据的写,大部分的线程只是都数据的话,volatile是一个不错的选项。包括前面的那个简单的示例,如果只是一个普通变量的访问,没有特殊要求,用volatile是一种很简便的解决方法。

        和用synchronized等线程同步机制来限制代码,volatile可以用一种很简单的方式来满足一些多线程访问需求。

对于volatile更多详细的应用可以参考这篇文章.

 

总结

        Volatile变量是一种可以在某种情况下简化多线程编程的手法。它限制了多线程访问的jit优化,在某些对性能要求比较高的情况下需要慎重考虑。

 

参考材料

1. Programming concurrency on the JVM

2. Java 理论与实践,正确使用volatile变量

  • 大小: 59 KB
分享到:
评论

相关推荐

    深入理解Java中的volatile关键字(总结篇)

    volatile这个关键字,不仅仅在Java语言中有,在很多语言中都有的,而且其用法和语义也都是不尽相同的。这篇文章主要介绍了Java中的volatile关键字,需要的朋友可以参考下

    Java中volatile关键字的作用与用法详解

    volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情。这篇文章主要介绍了Java中volatile关键字的作用与用法详解的相关资料,需要的朋友可以参考下

    谈谈Java中Volatile关键字的理解

    volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果,本文给大家介绍java中volatile关键字,需要的朋友参考下

    深入了解Java中Volatile关键字

    主要介绍了Java中Volatile关键字的相关知识,文章讲解非常详细,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下

    Java多线程并发编程 Volatile关键字

    volatile 关键字是一个神秘的关键字,也许在 J2EE 上的 JAVA 程序员会了解多一点,但在 Android 上的 JAVA 程序员大多不了解这个关键字。只要稍了解不当就好容易导致一些并发上的错误发生,例如好多人把 volatile ...

    Java并发编程volatile关键字的作用

    主要介绍了Java并发编程volatile关键字的作用,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下

    详细分析java并发之volatile关键字

    主要介绍了java并发之volatile关键字的的相关资料,文中代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下

    Java 关键字 volatile 的理解与正确使用

    本文主要介绍 volatile 的使用准则,以及使用过程中需注意的地方,感兴趣的朋友一起看看吧

    Java 多线程编程面试集锦20道问题解答Java多线程编程高难度面试题及解析

    您将了解线程安全、死锁、线程池、线程上下文切换、线程同步和互斥、volatile关键字、synchronized关键字等重要知识点。此外,还探讨了线程间通信、线程优先级、守护线程、线程组、可重入锁、线程局部变量等关键概念...

    java并发编程理论基础精讲

    本资源为您提供了关于 Java ...volatile 关键字: 解释 volatile 关键字的作用,探讨如何使用 volatile 实现线程间的可见性和有序性。 通过这份资源,将为您提供实用的指导,帮助您构建更稳定、可靠的多线程应用程序。

    Java面试题.docx

    16、说说你对Java反射的理解 17、说说你对Java注解的理解 18、Java中String的了解 19、String为什么要设计成不可变的? 20、Object类的equal和hashCode方法重写,为什么? 21-40题 21、List,Set,Map的区别 26...

    java并发编程

    , 这里,读者将通过使用java.lang.thread类、synchronized和volatile关键字,以及wait、notify和notifyall方法,学习如何初始化、控制和协调并发操作。此外,本书还提供了有关并发编程的全方位的详细内容,例如限制...

    在线 Java 硕士加薪课程 Term-05 班 (10.68G)

    1.Day01-jdk8新特性 ---【课时1】01-JDK8简介.mp4 ---【课时2】02-JDK8接口新特性应用.mp4 ---【课时3】03-JDK8中lambda表达式应用.mp4 ---【课时4】04-JDK8中的方法...---【课时6】06-volatile关键字应用分析.mp4 -

    Java-Interview:此项目为 Java 面试的汇总,多数是一些 Java 基础知识、底层原理、算法详解。也有上层应用设计,其中不乏一些大厂面试真题

    Java 知识点,继续完善中。 多数是一些 Java 基础知识、底层原理、算法详解。...你应该知道的 volatile 关键字 分布式相关 分布式限流 基于 Redis 的分布式锁 分布式缓存设计 分布式 ID 生成器 常用框架\

    Java-Interview:https

    Java 知识点,继续完善中。 多数是一些 Java 基础...你应该知道的 volatile 关键字 分布式相关 分布式限流 基于 Redis 的分布式锁 分布式缓存设计 分布式 ID 生成器 常用框架\第三方组件 Spring Bean 生命周期 Sprin

    java-interview

    Java 知识点,继续完善中。 多数是一些 Java 基础知识、底层原理、算法详解。...你应该知道的 volatile 关键字 分布式相关 分布式限流 基于 Redis 的分布式锁 分布式缓存设计 分布式 ID 生成器 常用框架\

    深入理解Java并发之synchronized实现原理.docx

    在 Java 中,关键字 synchronized可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时我们还应该注意到synchronized另外一个重要的作用,...

    93个netty高并发教学视频下载.txt

    83_AtomicIntegerFieldUpdater实例演练与volatile关键字分析;84_Netty引用计数注意事项与内存泄露检测方式;85_Netty编解码器剖析与入站出站处理器详解;86_Netty自定义编解码器与TCP粘包拆包问题;87_Netty编解码...

    精通并发与 netty 视频教程(2018)视频教程

    Netty引用计数的实现机制与自旋锁的使用技巧 82_Netty引用计数原子更新揭秘与AtomicIntegerFieldUpdater深度剖析 83_AtomicIntegerFieldUpdater实例演练与volatile关键字分析 84_Netty引用计数注意事项与内存泄露...

Global site tag (gtag.js) - Google Analytics