转载: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. 实验时css层叠样式表不更新的情况

    自定义了CSS的样式,希望在页面中起作用.因为MVC中Views/Shared/_Layout.cshtml是所有视图的公共文件,如下: <!DOCTYPE html> <html& ...

  2. cocos2dx mac下搭建android开发环境

    1)下载eclipse 地址:http://www.eclipse.org/downloads/ 2)安装adt 打开eclipse,菜单:help->install new software ...

  3. Juicy Couture_百度百科

    Juicy Couture_百度百科 Juicy Couture

  4. Hibernate 配置详解(12) 补充

    hibernate.hbm2ddl.import_files_sql_extractor 这个配置项用于补充这篇文章: http://blog.csdn.net/stefwu/article/deta ...

  5. HTML5新特性之CSS+HTML5实例

    1.新的DOCTYPE和字符集 HTML5的一项准则就是化繁为简,Web页面的DOCTYPE被极大的简化. <!DOCTYPE html> 同时字符集声明也被简化了: <meta c ...

  6. cuda核函数再调用核函数,多层并行

    #include <stdio.h> __global__ void childKernel(int i) { int tid = blockIdx.x*blockDim.x+thread ...

  7. angular学习(一):动态模板总结

    近期在项目中用到了angular,之前从未用到过此js lib库,因为项目也比較着急,学习的寥寥草草.到眼下为止也仅仅是学会皮毛而已,现将自己学习的知识总结例如以下: 备注1: 版本:1.2.2 备注 ...

  8. android 视频播放器的INTENT-FILTER属性

    <intent-filter>                   <action android:name="android.intent.action.VIEW&quo ...

  9. 如何在windows server 2012 R2 部署WEB项目

    tip: 今天发布项目到windows server 2012 R2上面. 没有接触过,其实很简单,看图: 这是安装IIS成功后显示的总图: 二.点击Manage ,选择Add Roles and F ...

  10. c 跟字符串有关的函数

    1.字符串查找 strstr char * strstr(const char *s1, const char *s2); 在s1中查找s2,如果找到返回首个s2的首地址 char * strcase ...