- 浏览: 846306 次
- 性别:
- 来自: 南京
文章分类
最新评论
-
loveseed1989:
您好,我用您的方法运行Cone.java,会给我报java.l ...
vtk学习笔记 --- 编译vtk库和java库 -
60love5:
<div class="quote_title ...
多线程中共享对象的可见性 -
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 3211要求:使用Oracle Streams捕获某个用户下部 ... -
基于Oracle Streams + Oracle AQ 捕获变更,发布变更(一)
2014-11-20 22:23 2797要求:使用Oracle Streams捕获某个用户下部分表 ... -
如何去掉在浏览器中打开java applet时的警告对话框
2013-08-24 12:10 6988好久没更新博客了! 最近,由于项目要求,需要将sw ... -
Android 内存泄露笔记
2013-03-05 23:10 01、大部分内存泄露都是错误的持有了Activity或者Con ... -
java 虚拟机总结 【思维导图】
2012-12-22 20:11 2092java虚拟机总结思维导图: 参考《深入理解jav ... -
Java虚拟机字节码执行引擎 【思维导图】
2012-12-22 19:51 1659java虚拟机字节码执行引擎思维导图总结: 参考《深入理 ... -
java 垃圾回收相关总结 【思维导图】
2012-12-21 19:03 3279java垃圾回收相关总结: 参考《深入理解java ... -
java并发中的延迟初始化
2012-12-12 19:17 4700在《java并发编程实战 ... -
java同步容器与并发容器
2012-12-09 18:07 4358何为同步容器:可以简 ... -
一个看似线程安全的示例
2012-12-09 14:56 5069在《java并发编程实战》第四章4.4.1节给出了一个程序示 ... -
多线程中的long和double
2012-12-08 19:26 4196在看一些代码的时候,会发现在定义long型和double型的 ... -
一个快速、轻量级 Collection 库 Trove
2012-12-07 09:35 3756Trove一个快速、轻量级针对java原子类型(byte,i ... -
java 中的Unsafe
2012-12-05 22:25 32744在阅读AtomicInteger的源码时,看到了这个类:su ... -
在ubuntu10上编译Thrift0.8.0
2012-08-01 15:34 2155下载thrift0.8.0 ,地址: http:// ... -
生活小工具--记账小助手1.0发布
2012-05-31 15:08 1727因为自己平时喜欢记账,把每日的消费情况都记录下来,所以希望找 ... -
话费速查升级版v1.3发布
2012-05-04 12:47 1435前段时间开发了一个话费速查的小应用,最近一直保持每周更新一个 ... -
编译zeromq的java绑定:jzmq
2012-05-03 22:47 135441、 下载zeromq源码:http://www.ze ... -
Android软件包静默安装小应用 - 附源码
2012-04-21 20:50 12414老早之前,写了一个android软件包静默安装的应用,放在工 ... -
最方便的联通话费,余额查询软件来了! --- 联通话费速查v1.2
2012-04-12 18:01 4032软件介绍: 联通话费速查是一款针对联通 ... -
android开发之定制标题栏 --- 附源码
2012-04-11 21:53 12149在开发上个应用 话费 ...
相关推荐
java多线程安全性基础介绍 线程安全 正确性 什么是线程安全性 原子性 竞态条件 i++ 读i ++ 值写回i 可见性 JMM 由于cpu和内存加载速度的差距,在两者之间增加了多级缓存导致,内存并不能直接对cpu可见。 ...
1. **理解Application对象的特性**:Application对象是ASP.NET中的一个全局性对象,它存储的数据对所有用户和请求都是可见的。这意味着多个线程可能同时尝试读取或写入Application对象,如果不加以控制,就可能导致...
Java多线程是Java编程中的一个重要概念,它允许程序同时执行多个任务,提高了程序的效率和响应速度。在Java中,实现多线程有两种主要方式:继承Thread类和实现Runnable接口。 1. 继承Thread类: 当我们创建一个新...
- volatile:修饰变量,确保多线程环境下的可见性和有序性,但不保证原子性。在实例中,可能用于共享标志的设置与读取。 - wait()、notify()和notifyAll():这些方法存在于Object类中,用于线程间的通信。在线程A...
在Android应用开发中,多线程技术是必不可少的,它能帮助开发者实现高效的代码执行,提升用户体验,并确保应用程序的响应性。本资源包主要聚焦于Android平台上的多线程编程,包括理论概念、最佳实践以及实际应用案例...
2. volatile关键字:保证了变量在多线程环境下的可见性和有序性,但不保证原子性。 3. Lock接口与ReentrantLock类:提供了比synchronized更细粒度的锁控制,具有可重入性、公平性等特点,可以配合Condition进行条件...
volatile确保了多线程环境下的变量可见性和有序性,但不保证原子性。 5. **死锁**:当两个或更多线程互相等待对方释放资源时,就会发生死锁。避免死锁的方法包括避免循环等待、设置超时、使用死锁检测算法等。 6. ...
Java内存模型定义了一套多线程访问内存和CPU的规则,以确保多线程环境下变量的可见性和顺序性。它规定了操作的先行发生规则,包括程序次序规则、管程锁定规则、volatile变量规则、线程启动规则、线程终止规则、对象...
3. **volatile关键字**:用于确保多个线程之间对共享变量的可见性,但不保证原子性。 4. **Lock接口与ReentrantLock**:提供比`synchronized`更细粒度的锁控制,支持公平锁和非公平锁,以及可中断的锁等待等。 **...
Linux多线程编程是计算机编程中一个高级主题,涉及到同时执行多个任务的能力,这些任务共享公共地址空间。它允许程序更有效地利用多核处理器的能力,提高性能和响应速度,优化资源利用,并改善程序的结构。Linux多...
Java多线程是Java编程中的一个核心概念,它允许程序同时执行多个任务,极大地提高了程序的效率和响应性。在Java中,多线程的实现主要有两种方式:继承Thread类和实现Runnable接口。 1. 继承Thread类:创建一个新的...
- `volatile`关键字确保线程间变量的可见性,但不保证原子性。 - `wait()`, `notify()`和`notifyAll()`方法用于线程间通信,需在同步环境中使用。 5. **线程通信问题** - 生产者-消费者问题:一个线程生产数据,...
通过对`volatile`关键字的工作原理及其在多线程环境中的应用进行深入探讨,我们可以更好地理解如何利用`volatile`来解决可见性和有序性问题。同时,我们也应该注意到`volatile`并不能保证操作的原子性,因此在需要...
易语言的多线程模块可能提供了互斥量、信号量、事件对象等同步机制,确保线程安全地访问共享数据。 3. **线程通信**:线程间通信是为了让线程之间能够交换信息。易语言可能提供了消息队列、共享内存或者基于事件的...
本文将深入探讨多线程的概念、重要性以及如何在实际项目中应用多线程,特别关注Java语言中的多线程实现。 多线程的基本概念: 1. 线程是操作系统调度的基本单位,一个进程中可以有多个线程并行执行。相比于单线程,...
随着**C++11** 的发布,标准库中包含了针对多线程编程的新组件,使得编写多线程应用程序变得更加简单。 #### 二、线程管理 - **`std::thread` 类**:这是**C++11** 中管理线程的主要方式。通过`std::thread`可以...
本指南详细介绍了多线程编程在iOS开发中的应用,希望读者能够从中受益,并在实际项目中有效地运用多线程技术。 #### 推荐资源 最后,推荐了一些学习多线程编程的资源,包括书籍、在线教程等,供有兴趣进一步深入...