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

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

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

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

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

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

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

  1. OS: Linux
  2. flag done set to true

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

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

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

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

  1. java -d32 RaceCondition

这次执行的结果会是:

  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的结构图来做下一步分析。

上面图中,每个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优化,在某些对性能要求比较高的情况下需要慎重考虑。

java volatile关键字的理解的更多相关文章

  1. Java Volatile关键字(转)

    出处:  Java Volatile关键字 Java的volatile关键字用于标记一个变量“应当存储在主存”.更确切地说,每次读取volatile变量,都应该从主存读取,而不是从CPU缓存读取.每次 ...

  2. Java volatile 关键字底层实现原理解析

    本文转载自Java volatile 关键字底层实现原理解析 导语 在Java多线程并发编程中,volatile关键词扮演着重要角色,它是轻量级的synchronized,在多处理器开发中保证了共享变 ...

  3. [Java并发编程(三)] Java volatile 关键字介绍

    [Java并发编程(三)] Java volatile 关键字介绍 摘要 Java volatile 关键字是用来标记 Java 变量,并表示变量 "存储于主内存中" .更准确的说 ...

  4. 13、Java并发性和多线程-Java Volatile关键字

    以下内容转自http://tutorials.jenkov.com/java-concurrency/volatile.html(使用谷歌翻译): Java volatile关键字用于将Java变量标 ...

  5. Java volatile关键字详解

    Java volatile关键字详解 volatile是java中的一个关键字,用于修饰变量.被此关键修饰的变量可以禁止对此变量操作的指令进行重排,还有保持内存的可见性. 简言之它的作用就是: 禁止指 ...

  6. java中volatile关键字的理解

    一.基本概念 Java 内存模型中的可见性.原子性和有序性.可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有 ...

  7. Java Volatile关键字

    在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写. 这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量 ...

  8. 从根源上解析 Java volatile 关键字的实现

    1.解析概览 内存模型的相关概念 并发编程中的三个概念 Java内存模型 深入剖析Volatile关键字 使用volatile关键字的场景 2.内存模型的相关概念 缓存一致性问题.通常称这种被多个线程 ...

  9. java volatile关键字解析

    volatile是什么 volatile在java语言中是一个关键字,用于修饰变量.被volatile修饰的变量后,表示这个变量在不同线程中是共享,编译器与运行时都会注意到这个变量是共享的,因此不会对 ...

随机推荐

  1. The method of using code coverage tool

    Please look at the following blog: http://blog.csdn.net/superqa/article/details/9060521 Use  ReportG ...

  2. C语言迭代求解

    date : 2013/8/12           desinger :pengxiaoen 今天看  国外电子信息科学经典教材系列   <电子电路分析与设计> 电子工业出版社的 的19 ...

  3. DelphiXe5中的双向绑定(使用使用TBindScope和TBindExpression,并覆盖AfterConstruction函数)

    在Delphi下等这一功能很久了,虽然C#下早已实现了这一功能.但是在Dephi下尝试这项功能时还是有些许的激动.闲言少絮,直接上代码. unit BindingDemo; interface use ...

  4. cocos2dx CCLayerColor和CCLayerColor

    在cocos2dx中,默认的CCLayer背景是黑色的,有些时候需要特殊的Layer,所以cocos2dx中提供了这两种Layer CCLayerColor是可以改变背景色的Layer,示例如下: C ...

  5. Highlight On Mouseover Effect With JQuery

    How to get the xpath by clicking an html element How to get the xpath by clicking an html element Qu ...

  6. Yii2简单纪要

    网上经常拿Yii来类比ROR,从MVC角度,使用体验及代码风格上确实有很多相似的地方.不过看配置文件发现Yii2不止是受rails的影响,同样有不少spring的影子,最明显的就是配置文件中很多IOC ...

  7. SSDP协议的Android实现以及使用

    前面一篇博客里面已经介绍过SSDP协议原理,本篇博客将实现实现Android上的SSDP协议. 关键技术分析:1.发送广播:须要发送送广播,所以须要使用MulticastSocket.SocketAd ...

  8. HTML元素的ID和Name属性的区别

    HTML元素的ID和Name属性的区别今天突然兴致来了,想深究下这两属性的具体区别最classical的答案:ID就像是一个人的身份证号码,而Name就像是他的名字,ID显然是唯一的,而Name是可以 ...

  9. 最近用的到的一些js的常用方法(简单的)

    由于新的项目开始了,是使用MVC 5.0 开发的,前端使用了两个主流的框架 UIKIT,Ignite UI(收费) 因为是mvc主要用json来交互,不能避免要对前端脚本进行操作,所以就将能用到的方法 ...

  10. Android 手机上安装并运行 Ubuntu 12.04

    ubuntu.sh脚本的原地址变动了,导致下载不了,现在更新了网盘地址.小技巧:遇到一些下载失效的时候可以试一试p2p下载工具(如 easyMule.迅雷等)试一试,说不定有人分享过~* —————— ...