转载请注明出处: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. sql 日记

    --4.选择雇用时间在1998-02-01到1998-05-01之间的员工姓名,job_id和雇用时间select last_name,job_id,hire_datefrom employeeswh ...

  2. table表格中单击添加动态编辑框

    var $newNode=$("<input type='text' style='width:250px; height:20px; maxlength='20' id='texti ...

  3. Unity 使用xLua遇到的坑

    在我们使用xLua作为Unity中lua集成的解决方案时,遇到了一个问题,就是当我们使用在lua中把UI中的某个控件绑定相应的事件(如按钮的onClick事件),xLua绑定这个事件是用委托实现的,具 ...

  4. 社群系统ThinkSNS V4.5.29 APP更新发布,新增用户认证及系统消息

    社群系统ThinkSNS V4版本移动端APP将于7月29日更新发布,本次更新修复部分bug,最主要是增加了移动端APP的用户认证功能,以及添加了系统消息,为V4.5.29版本发布.这两个功能PC端的 ...

  5. Linux内存详解

    --Linux内存详解 -----------------2014/05/24 Linux的内存上表现的不像windows那么直观,本文准备详细的介绍一下Linux的内存. 请看这下有linux命令f ...

  6. Linux学习总结(七)—— CentOS软件包管理:脚本安装

    脚本安装就是软件编写者写好一个shell脚本或者java脚本,你只需要输入一些简单的信息便可直接安装.这种安装方式方便简单,类似于Windows下软件的安装方式. 下面以webmin的安装为例讲解脚本 ...

  7. MySQL GROUP BY多个字段分组用法详解

    mysql语句中group by 很容易理解 是分组查询.比如 select sum(score) from user group by name 意思是查询每个人的分数总和但是, select su ...

  8. easyui datagrid自定义按钮列,即最后面的操作列(转)

    做项目的时候因为需求,要在表格的最后添加一列操作列,easyUI貌似没有提供这种功能,不过没关系,我们可以自定义来实现 版本:jQuery easyUI 1.3.2 这里我的实现方式是采用HTML形式 ...

  9. UWP 手绘视频创作工具 “来画Pro” 技术分享系列

    开篇先来说一下我和来画的故事,以及写这篇文章的初衷. 今年年初时,我还在北京,在 Face++,做着人脸识别技术的 Windows 和 Android 端,做着人工智能终将实现世间所有美好的梦.这时的 ...

  10. Django项目之小博客

    学习了几天Django,学着做了一个简易的小博客,主要用来练习Django基本的操作. 主要用到的命令和Django包.模块等: django.urls.url django.urls.import ...