转载请注明出处:http://blog.csdn.net/ns_code/article/details/17382679



《Java并发编程学习笔记之五:volatile变量修饰符—意料之外的问题》一文中遗留了一个问题,就是volatile只修饰了missedIt变量,而没修饰value变量,但是在线程读取value的值的时候,也读到的是最新的数据。但是在网上查了很多资料都无果,看来很多人对volatile的规则并不是太清晰,或者说只停留在很表面的层次,一知半解。

这两天看《深入Java虚拟机——JVM高级特性与最佳实践》第12章:Java内存模型与线程,并在网上查阅了Java内存模型相关资料,学到了不少东西,尤其在看这篇文章的volatile部分的讲解之后,算是确定了问题出现的原因。

首先明确一点:假如有两个线程分别读写volatile变量时,线程A写入了某volatile变量,线程B在读取该volatile变量时,便能看到线程A对该volatile变量的写入操作,关键在这里,它不仅会看到对该volatile变量的写入操作,A线程在写volatile变量之前所有可见的共享变量,在B线程读同一个volatile变量后,都将立即变得对B线程可见。

回过头来看文章中出现的问题,由于程序中volatile变量missedIt的写入操作在value变量写入操作之后,而且根据volatile规则,又不能重排序,因此,在线程B读取由线程A改变后的missedIt之后,它之前的value变量在线程A的改变也对线程B变得可见了。

我们颠倒一下value=50和missedIt=true这两行代码试下,即missedIt=true在前,value=50在后,这样便会得到我们想要的结果:value值的改变不会被看到。

这应该是JDK1.2之后对volatile规则做了一些修订的结果。

修改后的代码如下:

  1. public class Volatile extends Object implements Runnable {
  2. //value变量没有被标记为volatile
  3. private int value;
  4. //missedIt变量被标记为volatile
  5. private volatile boolean missedIt;
  6. //creationTime不需要声明为volatile,因为代码执行中它没有发生变化
  7. private long creationTime;
  8. public Volatile() {
  9. value = 10;
  10. missedIt = false;
  11. //获取当前时间,亦即调用Volatile构造函数时的时间
  12. creationTime = System.currentTimeMillis();
  13. }
  14. public void run() {
  15. print("entering run()");
  16. //循环检查value的值是否不同
  17. while ( value < 20 ) {
  18. //如果missedIt的值被修改为true,则通过break退出循环
  19. if  ( missedIt ) {
  20. //进入同步代码块前,将value的值赋给currValue
  21. int currValue = value;
  22. //在一个任意对象上执行同步语句,目的是为了让该线程在进入和离开同步代码块时,
  23. //将该线程中的所有变量的私有拷贝与共享内存中的原始值进行比较,
  24. //从而发现没有用volatile标记的变量所发生的变化
  25. Object lock = new Object();
  26. synchronized ( lock ) {
  27. //不做任何事
  28. }
  29. //离开同步代码块后,将此时value的值赋给valueAfterSync
  30. int valueAfterSync = value;
  31. print("in run() - see value=" + currValue +", but rumor has it that it changed!");
  32. print("in run() - valueAfterSync=" + valueAfterSync);
  33. break;
  34. }
  35. }
  36. print("leaving run()");
  37. }
  38. public void workMethod() throws InterruptedException {
  39. print("entering workMethod()");
  40. print("in workMethod() - about to sleep for 2 seconds");
  41. Thread.sleep(2000);
  42. //仅在此改变value的值
  43. missedIt = true;
  44. //      value = 50;
  45. print("in workMethod() - just set value=" + value);
  46. print("in workMethod() - about to sleep for 5 seconds");
  47. Thread.sleep(5000);
  48. //仅在此改变missedIt的值
  49. //      missedIt = true;
  50. value = 50;
  51. print("in workMethod() - just set missedIt=" + missedIt);
  52. print("in workMethod() - about to sleep for 3 seconds");
  53. Thread.sleep(3000);
  54. print("leaving workMethod()");
  55. }
  56. /*
  57. *该方法的功能是在要打印的msg信息前打印出程序执行到此所化去的时间,以及打印msg的代码所在的线程
  58. */
  59. private void print(String msg) {
  60. //使用java.text包的功能,可以简化这个方法,但是这里没有利用这一点
  61. long interval = System.currentTimeMillis() - creationTime;
  62. String tmpStr = "    " + ( interval / 1000.0 ) + "000";
  63. int pos = tmpStr.indexOf(".");
  64. String secStr = tmpStr.substring(pos - 2, pos + 4);
  65. String nameStr = "        " + Thread.currentThread().getName();
  66. nameStr = nameStr.substring(nameStr.length() - 8, nameStr.length());
  67. System.out.println(secStr + " " + nameStr + ": " + msg);
  68. }
  69. public static void main(String[] args) {
  70. try {
  71. //通过该构造函数可以获取实时时钟的当前时间
  72. Volatile vol = new Volatile();
  73. //稍停100ms,以让实时时钟稍稍超前获取时间,使print()中创建的消息打印的时间值大于0
  74. Thread.sleep(100);
  75. Thread t = new Thread(vol);
  76. t.start();
  77. //休眠100ms,让刚刚启动的线程有时间运行
  78. Thread.sleep(100);
  79. //workMethod方法在main线程中运行
  80. vol.workMethod();
  81. } catch ( InterruptedException x ) {
  82. System.err.println("one of the sleeps was interrupted");
  83. }
  84. }
  85. }

执行结果如下:

很明显,这其实并不符合使用volatile的第二个条件:附上一篇讲述volatile关键字正确使用的很好的文章:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html





转: 【Java并发编程】之十八:第五篇中volatile意外问题的正确分析解答(含代码)的更多相关文章

  1. 转: 【Java并发编程】之三:线程挂起、恢复与终止的正确方法(含代码)

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17095733 挂起和恢复线程     Thread 的API中包含两个被淘汰的方法,它们用 ...

  2. java并发编程(十八)阻塞队列和阻塞栈

    阻塞队列 阻塞队列是Java 5并发新特性中的内容,阻塞队列的接口是java.util.concurrent.BlockingQueue,它有多个实现类:ArrayBlockingQueue.Dela ...

  3. java并发编程(十八)----(线程池)java线程池框架Fork-Join

    还记得我们在初始介绍线程池的时候提到了Executor框架的体系,到现在为止我们只有一个没有介绍,与ThreadPoolExecutor一样继承与AbstractExecutorService的For ...

  4. java并发编程笔记(八)——死锁

    java并发编程笔记(八)--死锁 死锁发生的必要条件 互斥条件 进程对分配到的资源进行排他性的使用,即在一段时间内只能由一个进程使用,如果有其他进程在请求,只能等待. 请求和保持条件 进程已经保持了 ...

  5. Java并发编程系列-(9) JDK 8/9/10中的并发

    9.1 CompletableFuture CompletableFuture是JDK 8中引入的工具类,实现了Future接口,对以往的FutureTask的功能进行了增强. 手动设置完成状态 Co ...

  6. Java并发编程入门,看这一篇就够了

    Java并发编程一直是Java程序员必须懂但又是很难懂的技术内容.这里不仅仅是指使用简单的多线程编程,或者使用juc的某个类.当然这些都是并发编程的基本知识,除了使用这些工具以外,Java并发编程中涉 ...

  7. java并发编程工具类JUC第四篇:LinkedBlockingQueue链表队列

    在之前的文章中已经为大家介绍了java并发编程的工具:BlockingQueue接口.ArrayBlockingQueue.DelayQueue. LinkedBlockingQueue 队列是Blo ...

  8. java并发编程工具类JUC第七篇:BlockingDeque双端阻塞队列

    在之前的文章中已经为大家介绍了java并发编程的工具:BlockingQueue接口.ArrayBlockingQueue.DelayQueue.LinkedBlockingQueue.Priorit ...

  9. Java并发编程(十)阻塞队列

    使用非阻塞队列的时候有一个很大问题就是:它不会对当前线程产生阻塞,那么在面对类似消费者-生产者的模型时,就必须额外地实现同步策略以及线程间唤醒策略,这个实现起来就非常麻烦.但是有了阻塞队列就不一样了, ...

随机推荐

  1. 生成二维码的js以及调用打印插件

    插件: qrcode.js 插件下载网址:http://code.ciaoca.com/javascript/qrcode/ 用法实例: <script type="text/java ...

  2. PAT (Basic Level) Practise (中文) 1016. 部分A+B (15)

    1016. 部分A+B (15) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue 正整数A的"DA(为1 ...

  3. python实战===生成随机数

        用于生成一个指定范围内的随机符点数,两个参数其中一个是上限,一个是下限.   import random print random.uniform(10, 20) print random.u ...

  4. ubuntu下apache2-php-mysql的环境配置

    基本的支持环境.暂时还不应用zend优化,因此这里就不涉及到zend optimizer的安装了.其实在ubuntu系统中中安装远比在windows系统中设置更为容易,而且在终端下设置更省事. 1.安 ...

  5. BZOJ 3028 食物 生成函数

    Description 明明这次又要出去旅游了,和上次不同的是,他这次要去宇宙探险!我们暂且不讨论他有多么NC,他又幻想了他应 该带一些什么东西.理所当然的,你当然要帮他计算携带N件物品的方案数.他这 ...

  6. Spring Web MVC(一)

    [toc] 概述 Spring的web框架围绕DispatcherServlet设计. DispatcherServlet的作用是将请求分发到不同的处理器. Spring的web框架包括可配置的处理器 ...

  7. iptables规则的删除-怎么删除一条已有的iptables规则

    语法是: iptables -D chain rulenum [options]     其中: chain 是链的意思,就是INPUT FORWARD 之类的定语     rulenum 是该条规则 ...

  8. 博文Contents<1--到450—>

    积分=排名>2017-05-15这一天还真是厉害了.让我等了5个月时间... ====================-------------- 前言:博客中的随笔文章.并非都是笔者的原创文章 ...

  9. JavaScript看书笔记01

    JavaScript看书笔记... ------------------- JavaScript允许var定义语句出现在函数内部的任意位置.JS中使用Var来显示的给变量声明 JavaScript是一 ...

  10. MyBatis记录

    记录一下MyBatis的几个模块大纲,除去缓存以及集合映射两个部分 Mybatis架构 1. mybatis配置 SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了myb ...