- 浏览: 837900 次
- 性别:
- 来自: 南京
文章分类
最新评论
-
loveseed1989:
您好,我用您的方法运行Cone.java,会给我报java.l ...
vtk学习笔记 --- 编译vtk库和java库 -
60love5:
60love5 写道首先谢谢你的解析,但你这个验证可见性的小程 ...
多线程中共享对象的可见性 -
60love5:
首先谢谢你的解析,但你这个验证可见性的小程序是存在问题的,你的 ...
多线程中共享对象的可见性 -
Gamehu520:
...
java 中的Unsafe -
shanpao1234560:
这个list不是静态的第一种情况下也会有线程安全的问题么,求指 ...
一个看似线程安全的示例
在阅读《java并发编程实战》的第三章的时候,看到书中的一个例子,随在Eclipse中执行看看效果。示例代码如下:
public class NoVisibility { private static boolean ready; private static int number; private static class ReaderThread extends Thread { public void run() { while(!ready) { Thread.yield(); } System.out.println(number); } } public static void main(String[] args) { new ReaderThread().start(); number = 42; ready = true; } }
书中的解释是,这个程序的执行结果除了打印出42以外,还有两外另种情况:一时无限循环下去,二是打印出0来。其中打印42很好理解,因为,在启动ReaderThread线程以后,如果还没有设置ready为true,那么ReaderThread会一直循环,直到读取ready的值为true,然后打印出结果42来。
无限循环这个结果,通过代码来测试是很难观察到的,但是通过书中的分析,这种情况是存在的。这个示例中的number和ready,会被两个线程访问,其一是运行该程序的main线程,其二是ReaderThread线程,所以这两个变量可以称之为共享变量,由于java虚拟机自己的缓存机制,在缺少同步的情况下,会将number和ready的数值缓存在寄存器中,这就会导致ReaderThread线程在某些情况下读取不到最新的值,这样即使在main方法中将ready设置为true了,但是ReaderThread线程读取的ready值仍然为false,这样就会导致一直循环下去。
上面的这个示例很难观察到这种情况,所以我对其进行了改造,可以看到另外一种与我们预期不相符的情况:
public class NoVisibility { private static boolean ready; private static int number; private static class ReaderThread extends Thread { public void run() { while(!ready) { System.out.println(System.currentTimeMillis()); Thread.yield(); } System.out.println(number); } } public static void main(String[] args) { for(int i=0;i < 100;i++) new ReaderThread().start(); number = 42; ready = true; } }
对示例代码做了两处改动,第一,在ReaderThread线程的while循环中打印当前时间,第二,在main方法中增加了一个循环,创建了100个ReaderThread线程并启动线程。然后我们看看运行结果:(这里只列出了部分结果)
... 1354960834108 42 1354960834108 1354960834108 42 1354960834108 42 1354960834108 1354960834108 1354960834108 1354960834108 42 42 ...
从打印的结果来看,就会发现问题,为什么在某些线程打印了42以后,有些线程仍然在打印时间?
如果某个线程打印出了42,说明main方法已经执行完毕,即变量ready的值已经设置为true了,那么这以后其它的线程打印的结果应该都是42了,但这里的结果是有些线程读取的ready值仍然为false,这就说明了java虚拟机会对线程中使用到的变量进行缓存,所以就出问题了。
java虚拟机缓存变量,是出于性能的考虑,并且在单线程程序中,或者不存在共享变量的多线程程序中,这都不会出现问题。但是,在有共享变量的多线程程序中,就会发生问题,这里就涉及到共享对象的可见性了,也就是在没有使用同步机制的情况下,一个线程对某个共享对象的修改,并不会立即被其它的线程读取到。上面的代码之所以会出问题,就是因为ReaderThread线程,没有读取到main线程对ready变量修改后的值。要解决上述问题,可以通过在main方法和ReaderThread线程中的run方法中,给访问number和ready值的代码块中加锁来解决。
另外一种结果打印出0来,这个暂时还不是很明白,书中的解释是java虚拟机的内存模型允许编译器对操作顺序进行重排序,并将数值缓存在寄存器中,这样可能在读取到ready修改后的值后,却仍然读取了number的旧值,从而打印出了int的默认值0来。
评论
public class NoVisibility { private static boolean ready; private static int number; private static final AtomicLong count = new AtomicLong(0); private static class ReaderThread extends Thread { public ReaderThread(String name) { super(name); } public void run() { while (!ready) { System.out.println(Thread.currentThread().getName() + " : " + count.incrementAndGet() + " # " + System.currentTimeMillis()); Thread.yield(); } System.out.println(Thread.currentThread().getName() + " : " + count.incrementAndGet() + " # " + number + " # " + System.currentTimeMillis()); } } public static void main(String[] args) { Thread.currentThread().setPriority(10); for (int i = 0; i < 1000; i++) { Thread thread = new ReaderThread("Thread-" + i); thread.start(); } number = 42; ready = true; System.out.println("--- main-Thread Over : " + System.currentTimeMillis() + " ---"); } }/* Output: (输出片段,结果不唯一:) Thread-482 : 794 # 1481685379575 Thread-485 : 2604 # 42 # 1481685379625 Thread-482 : 2605 # 42 # 1481685379625 Thread-487 : 793 # 1481685379575 Thread-486 : 792 # 1481685379575 Thread-480 : 791 # 1481685379575 Thread-483 : 790 # 1481685379575 *///:~
public class NoVisibility { private static boolean ready; private static int number; private static final AtomicLong count = new AtomicLong(0); private static class ReaderThread extends Thread { public ReaderThread(String name) { super(name); } public void run() { while (!ready) { System.out.println(Thread.currentThread().getName() + " : " + count.incrementAndGet() + " # " + System.currentTimeMillis()); Thread.yield(); } System.out.println(Thread.currentThread().getName() + " : " + count.incrementAndGet() + " # " + number + " # " + System.currentTimeMillis()); } } public static void main(String[] args) { Thread.currentThread().setPriority(10); for (int i = 0; i < 1000; i++) { Thread thread = new ReaderThread("Thread-" + i); thread.start(); } number = 42; ready = true; System.out.println("--- main-Thread Over : " + System.currentTimeMillis() + " ---"); } }
这样也不能说明是JVM做了缓存才出现的问题,因为代码中跑了100条线程,有可能出现一种情况是,很多线程在执行System.out.println(System.currentTimeMillis()); 时,刚好某个线程A打印了42,这时代表主线程执行完毕,此时ready=true,然后其它线程再执行while(!ready)时判断为false,执行打印语句。
我对你原来的做了修改
private static boolean ready;
private static int number;
private static AtomicInteger ss = new AtomicInteger();
private static class ReaderThread extends Thread {
public void run() {
while(!ready) {
System.out.println(System.currentTimeMillis());
Thread.yield();
}
System.out.println(ss.incrementAndGet()+"_"+number);
}
}
public static void main(String[] args) {
for(int i=0;i < 100;i++)
new ReaderThread().start();
number = 42;
ready = true;
}
加了一个ss变量显示打印的次数,刚好打印的是100次,如果按你的分析,它打印的次数有可能不是100次
早期的jdk如1.2,1.3 允许无序写入。。
代码和实际执行顺序不一致,如下列代码,
number = 42;
ready = true;
----------------------------------------
可能执行的顺序是:
ready = true;
number = 42;
这样number还没初始化,就打印出0了
是的,早期的jdk中存在,新的版本貌似解决了,但是没找到明确解释。
貌似没有看到JDK有这方面的改动。重排序有个as-if-serial语义原则。就是如果交换顺序不会对下方的的语句产生影响就可以执行重排序,遵循了这种原则的情况下感觉任然是按照顺序执行。应该还是可能出现0这种情况的。我也是刚好看到这块,大家讨论下。http://www.infoq.com/cn/articles/java-memory-model-2
早期的jdk如1.2,1.3 允许无序写入。。
代码和实际执行顺序不一致,如下列代码,
number = 42;
ready = true;
----------------------------------------
可能执行的顺序是:
ready = true;
number = 42;
这样number还没初始化,就打印出0了
是的,早期的jdk中存在,新的版本貌似解决了,但是没找到明确解释。
早期的jdk如1.2,1.3 允许无序写入。。
代码和实际执行顺序不一致,如下列代码,
number = 42;
ready = true;
----------------------------------------
可能执行的顺序是:
ready = true;
number = 42;
这样number还没初始化,就打印出0了
发表评论
-
基于Oracle Streams + Oracle AQ 捕获变更,发布变更(二)
2014-11-21 22:23 3164要求:使用Oracle Streams捕获某个用户下部 ... -
基于Oracle Streams + Oracle AQ 捕获变更,发布变更(一)
2014-11-20 22:23 2715要求:使用Oracle Streams捕获某个用户下部分表 ... -
如何去掉在浏览器中打开java applet时的警告对话框
2013-08-24 12:10 6948好久没更新博客了! 最近,由于项目要求,需要将sw ... -
Android 内存泄露笔记
2013-03-05 23:10 01、大部分内存泄露都是错误的持有了Activity或者Con ... -
java 虚拟机总结 【思维导图】
2012-12-22 20:11 2055java虚拟机总结思维导图: 参考《深入理解jav ... -
Java虚拟机字节码执行引擎 【思维导图】
2012-12-22 19:51 1592java虚拟机字节码执行引擎思维导图总结: 参考《深入理 ... -
java 垃圾回收相关总结 【思维导图】
2012-12-21 19:03 3235java垃圾回收相关总结: 参考《深入理解java ... -
java并发中的延迟初始化
2012-12-12 19:17 4639在《java并发编程实战 ... -
java同步容器与并发容器
2012-12-09 18:07 4299何为同步容器:可以简 ... -
一个看似线程安全的示例
2012-12-09 14:56 4970在《java并发编程实战》第四章4.4.1节给出了一个程序示 ... -
多线程中的long和double
2012-12-08 19:26 4124在看一些代码的时候,会发现在定义long型和double型的 ... -
一个快速、轻量级 Collection 库 Trove
2012-12-07 09:35 3694Trove一个快速、轻量级针对java原子类型(byte,i ... -
java 中的Unsafe
2012-12-05 22:25 32672在阅读AtomicInteger的源码时,看到了这个类:su ... -
在ubuntu10上编译Thrift0.8.0
2012-08-01 15:34 2104下载thrift0.8.0 ,地址: http:// ... -
生活小工具--记账小助手1.0发布
2012-05-31 15:08 1647因为自己平时喜欢记账,把每日的消费情况都记录下来,所以希望找 ... -
话费速查升级版v1.3发布
2012-05-04 12:47 1376前段时间开发了一个话费速查的小应用,最近一直保持每周更新一个 ... -
编译zeromq的java绑定:jzmq
2012-05-03 22:47 134911、 下载zeromq源码:http://www.ze ... -
Android软件包静默安装小应用 - 附源码
2012-04-21 20:50 12343老早之前,写了一个android软件包静默安装的应用,放在工 ... -
最方便的联通话费,余额查询软件来了! --- 联通话费速查v1.2
2012-04-12 18:01 3953软件介绍: 联通话费速查是一款针对联通 ... -
android开发之定制标题栏 --- 附源码
2012-04-11 21:53 12085在开发上个应用 话费 ...
相关推荐
java多线程安全性基础介绍 线程安全 正确性 什么是线程安全性 原子性 竞态条件 i++ 读i ++ 值写回i 可见性 JMM 由于cpu和内存加载速度的差距,在两者之间增加了多级缓存导致,内存并不能直接对cpu可见。 ...
共享资源与竞态条件: 详解共享资源在多线程环境中的问题,引出竞态条件的概念。 对象锁和监视器: 介绍对象锁的概念,解释如何使用 synchronized 关键字来实现对象级别的同步。 线程间通信: 详细讲解多线程之间...
第3章 共享对象 3.1 可见性 3.2 发布和逸出 3.3 线程封闭 3.4 不可变性 3.5 安全发布 . 第4章 组合对象 4.1 设计线程安全的类 4.2 实例限制 4.3 委托线程安全 4.4 向已有的线程安全类添加功能 4.5 同步策略的文档化 ...
可见性-volatile 通过内存屏障和禁止重排序优化实现 1.对volatile变量写操时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存 2.对volatile变量读操时,会在读操作后加入一条load屏障指令,...
3.5.6 安全地共享对象 第4章 对象的组合 4.1 设计线程安全的类 4.1.1 收集同步需求 4.1.2 依赖状态的操作 4.1.3 状态的所有权 4.2 实例封闭 4.2.1 Java监视器模式 4.2.2 示例:车辆追踪 4.3 线程安全性的...
3.5.6 安全地共享对象 第4章 对象的组合 4.1 设计线程安全的类 4.1.1 收集同步需求 4.1.2 依赖状态的操作 4.1.3 状态的所有权 4.2 实例封闭 4.2.1 Java监视器模式 4.2.2 示例:车辆追踪 4.3 线程安全性的...
本文介绍了不变对象是在实例化后其外部可见状态无法更改的对象。Java类库中的String、Integer和BigDecimal类就是不变对象的示例-它们表示在对象的生命期内无法更改的单个值。并说明了在Java理论与实践中,不变性的...
在多线程的操作中,一个对象会被多个线程共享或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把它声明为final,意图使得它"永远不变".其实那是...
* 最后的加餐环节是带着你理解 Java 内存模型:见识多线程读写共享数据的原子性、可见性、有序性,以及很多人解释不清楚的 happens-before 规则。当然还不能少了 CAS 和 synchronized 优化。 主讲内容 第一章:...
多视点方法也是管理需求变化的一种新方法,它可以用于管理不一致性, 并进行关于变化的推理。 2. M公司的软件产品以开发实验型的新软件为主。用瀑布模型进行软件开发已经有近十年了,并取得了一些成功。若你作为一...
2.6.1 名字的可见性 2.6.2 使用其他组件 2.6.3 static关键字 2.7 我们的第一个Java程序 2.8 注释和嵌入文档 2.8.1 注释文档 2.8.2 具体语法 2.8.3 嵌入HTML 2.8.4 @see:引用其他类 2.8.5 类文档标记 2.8.6 变量文档...
service不包含可见的用户界面,而是在后台无限地运行可以连接到一个正在运行的服务中,连接后,可以通过服务中暴露出来的借口与其进行通信 broadcast receiver是一个接收广播消息并作出回应的component,broadcast ...
2.6.1 名字的可见性 2.6.2 使用其他组件 2.6.3 static关键字 2.7 我们的第一个Java程序 2.8 注释和嵌入文档 2.8.1 注释文档 2.8.2 具体语法 2.8.3 嵌入HTML 2.8.4 @see:引用其他类 2.8.5 类文档标记 2.8.6 变量文档...
2.6.1 名字的可见性 2.6.2 使用其他组件 2.6.3 static关键字 2.7 我们的第一个Java程序 2.8 注释和嵌入文档 2.8.1 注释文档 2.8.2 具体语法 2.8.3 嵌入 2.8.4 @see:引用其他类 2.8.5 类文档标记 2.8.6 变量文档标记...
2.18.3 可见性表示符 62 2.18.4 友类 62 2.18.5 对象的秘密 63 2.18.6 TObject:所有对象的祖先 63 2.18.7 接口 63 2.19 结构化异常处理 66 2.19.1 异常类 68 2.19.2 执行的流程 70 2.19.3 重新触发异常 71 2.20 ...
多线程,用什么关键字修饰同步方法?stop()和suspend()方法为何不推荐使用? 59.使用socket建立客户端与服务器的通信的过程 60.JAVA语言国际化应用,Locale类,Unicode 61.描述反射机制的作用 62.如何读写一个...
2.6.1 名字的可见性 2.6.2 使用其他组件 2.6.3 static关键字 2.7 我们的第一个Java程序 2.8 注释和嵌入文档 2.8.1 注释文档 2.8.2 具体语法 2.8.3 嵌入HTML 2.8.4 @see:引用其他类 2.8.5 类文档标记 ...
2.6.1 名字的可见性 2.6.2 使用其他组件 2.6.3 static关键字 2.7 我们的第一个Java程序 2.8 注释和嵌入文档 2.8.1 注释文档 2.8.2 具体语法 2.8.3 嵌入HTML 2.8.4 @see:引用其他类 2.8.5 类文档标记 2.8.6 变量文档...