zoukankan      html  css  js  c++  java
  • java volatile关键字的理解

    转载:http://shmilyaw-hotmail-com.iteye.com/blog/1672779

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

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

    Java代码  收藏代码
    1. public class RaceCondition {              
    2.   private static boolean done;  
    3.     
    4.   public static void main(final String[] args) throws InterruptedException{  
    5.     new Thread(  
    6.       new Runnable() {  
    7.         public void run() {  
    8.       int i = 0;  
    9.           while(!done) { i++; }  
    10.             System.out.println("Done!");  
    11.           }  
    12.         }  
    13.     ).start();  
    14.   
    15.     System.out.println("OS: " + System.getProperty("os.name"));    
    16.     Thread.sleep(2000);  
    17.     done = true;  
    18.     System.out.println("flag done set to true");  
    19.   }  
    20. }  

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

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

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

    Java代码  收藏代码
    1. OS: Linux  
    2. flag done set to true  

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

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

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

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

    Java代码  收藏代码
    1. java -d32 RaceCondition  

    这次执行的结果会是:

    Java代码  收藏代码
    1. OS: Linux  
    2. flag done set to true  
    3. 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更多详细的应用可以参考这篇文章.

       应用场景推荐:

       变量的值不依赖于以前的值:比如I++这种操作

       作为状态标志:比如boolean类型的变量

       在ReentrantLock中的使用volatile变量在表示状态

    总结

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

  • 相关阅读:
    leetcode 309. Best Time to Buy and Sell Stock with Cooldown
    leetcode 714. Best Time to Buy and Sell Stock with Transaction Fee
    leetcode 32. Longest Valid Parentheses
    leetcode 224. Basic Calculator
    leetcode 540. Single Element in a Sorted Array
    leetcode 109. Convert Sorted List to Binary Search Tree
    leetcode 3. Longest Substring Without Repeating Characters
    leetcode 84. Largest Rectangle in Histogram
    leetcode 338. Counting Bits
    git教程之回到过去,版本对比
  • 原文地址:https://www.cnblogs.com/googlemeoften/p/5769048.html
Copyright © 2011-2022 走看看