可见性问题实例

说到并发安全时,我们常提及可见性的问题,通俗点讲就是线程1看不到线程2写入变量v的值(更专业的解释以及是什么导致可见性问题,又该如何解决,见扩展阅读),但一直偏于理论,实际中有没有因可见性而导致问题的例子呢?回答是肯定的,接下来我们一起来看几个例子。

这个例子很简单,新建的线程里有一个普通变量stop,用来表示是否结束循环里的自增操作。主线程启动这个线程后,将该变量置为true,观察线程是否打印出finish loop那行,如果存在可见性问题,主线程修改stop值为true,线程v看stop的值应该还是false。

查看源代码

打印帮助

01 class VisibilityThread extends Thread {
02     private boolean stop;
03  
04     public void run() {
05         int i = 0;
06         System.out.println("start loop.");
07         while(!getStop()) {
08             i++;
09         }
10         System.out.println("finish loop,i=" + i);
11     }
12  
13     public void stopIt() {
14         stop = true;
15     }
16  
17     public boolean getStop(){
18         return stop;
19     }
20 }
21  
22 public class VisibilityTest {
23     public static void main(String[] args) throws Exception {
24         VisibilityThread v = new VisibilityThread();
25         v.start();
26  
27         Thread.sleep(1000);//停顿1秒等待新启线程执行
28         System.out.println("即将置stop值为true");
29         v.stopIt();
30         Thread.sleep(1000);
31         System.out.println("finish main");
32         System.out.println("main中通过getStop获取的stop值:" + v.getStop());
33     }
34 }

我们先来执行一遍(操作系统:XP,下同。JDK:见图示):

执行结果如上图,有人该问了,线程v最终停下来了,这不是表示它看到stop值为true了吗?是的,确实如此。但让我们再看一个这个程序的执行结果。

这一次,我们发现程序一直未能结束,表示线程v看到stop的值是false,但是主线程打印出的值却是true。

对比两次的执行方式,我们发现后一次加上了-server选项。显示version的时候也由Client VM变成了Server VM。那么Client VM与Server VM有什么区别在哪里?简单地讲,Client VM启动时做了一般优化,耗时少,启动快,但程序执行的也相对也较慢;Server VM启动的时候做了更多优化,耗时多,启动慢,但程序执行快。如果在运行java命令的时候没有指定具体模式的时候,会有一个默认值,这个默认值随硬件和操作系统的不同而不同,这里有张JDK 1.6在各平台默认VM模式的图。

我们再来看个例子,这个例子源于hotspot VM的一个bug:

查看源代码

打印帮助

01 public class InterruptedVisibilityTest {
02     public void think() {
03         System.out.println("新线程正在执行");
04         while (true) {
05             if (checkInterruptedStatus()) break;
06         }
07         System.out.println("新线程退出循环");
08     }
09  
10     private boolean checkInterruptedStatus() {
11         return Thread.currentThread().isInterrupted();
12     }
13  
14     public static void main(String[] args) throws Exception {
15         final InterruptedVisibilityTest test = new InterruptedVisibilityTest();
16         Thread thinkerThread = new Thread("Thinker") {
17             public void run() {
18                 test.think();
19             }
20         };
21         thinkerThread.start();
22         Thread.sleep(1000);//等待新线程执行
23         System.out.println("马上中断thinkerThread");
24         thinkerThread.interrupt();
25         System.out.println("已经中断thinkerThread");
26         thinkerThread.join(3000);
27         if (thinkerThread.isAlive()) {
28             System.err.println("thinkerThread未能在中断后3s停止");
29             System.err.println("JMV bug");
30             System.err.println("主线程中检测thinkerThread的中断状态:" + thinkerThread.isInterrupted());
31         }
32     }
33 }

这个例子也很简单,thinkerThread一直检查中断状态,主线程在启动thinkerThread之后的某个时刻调用interrupt中断thinkerThread。在《The Java Language Specification Java SE 7 Edition》§17.4.4中我们能够看到,如果线程1调用线程2的interrupt方法,那么所有线程(包括线程2)都能通过Thread.isInterrupted方法来检测到这个中断状态。这里直接用hotspot VM的-server模式执行一下,结果如下图:

thinkerThread没能退出循环,没看到主线程所置的中断状态。

后面这个例子是hotpost VM的一个bug导致的,在最新的hotspot中应该已经被修复了(笔者未测试最新版)。其它VM如IBM J9,JRockit,harmony等并没有发现这样的bug。说这是bug,是因为JLS中规定了main发出的中断必须对thinkerThread可见。但是,如第一个例子,则不是bug,因为JLS是允许这种行为的。当在第一个例子的循环中的i++后面加上一句Thread.yield()调用(该调用在规范中并没有特殊内存语义),这我使用的这个版本的VM上,就看不到可见性问题了。这也说明,JVM的优化是无法预知的,允许可见性的地方不一定就真会出现或一直出现。

JLS允许未充分同步的代码出现可见性问题,但是某个实际的JVM完全可以实现的比JLS上规定的更强,比如不允许可见性问题出现,那么,在这样的JVM上就展现不出这样的问题了。第一个例子这里只是运行在hotpost下,也许在其它JVM下同样采用最优化的方式执行,可能并不会出现这里的问题。

在我们编码的时候,也许并不知道代码会跑在什么样的系统上,不知道会采用什么样的JVM,为了使得写出的代码更健壮,我们只能按照规范所规定的最低保证去编码,要避免这类问题,只有保证代码充分同步,避免数据争用,而不应该依赖于某个具体JVM实现。即使是具体的某款JVM,不同的版本间也可能存在着差异。

最后,这样的例子启发我们,测试代码的时候应尽可能启用各JVM的最佳优化模式。

扩展阅读:

至此,我们已经了解到实际中多线程运行真的会出现这样的场景。为什么会出现可见性问题?有什么解决方案?下面链接中的内容为我们提供了专业的解答。

时间: 2024-11-03 21:01:21

可见性问题实例的相关文章

API Demos 2.3 学习笔记 (9)-- Views->Visibility

更多精彩内容,请点击阅读:<API Demos 2.3 学习笔记> Visibility 示例以TextView为例介绍了View的三种可见性以及如何设置View的可见性.这些可见性的设置方法同样适用于View以及其他继承自View的子类对象.从示例布局文件来看,主要分为两部分,一部分为一个线性垂直布局,包含三个不同背景色的TextVew对象:另一部分,为一个线性水平布局,包含三个Button对象. View的可见性主要分为三种, VISIBLE(可见). INVISIBLE(不可见). GO

全面解析Java支持的数据类型及Java的常量和变量类型_java

基本数据类型变量就是用来储存值而保留的内存位置.这就意味着当你创建一个变量时就会在内存中占用一定的空间. 基于变量的数据类型,操作系统会进行内存分配并且决定什么将被储存在保留内存中.因此,通过给变量分配不同的数据类型,你可以在这些变量中存储整数,小数或者字字母. Java 中有两种有效地数据类型: 原始数据类型 引用数据类型 原始数据类型 Java 支持 8 种原始数据类型.原始数据类型是由该语言预先定义的并用关键词命名的.下面让我们深入学习一下这 8 种数据类型. 字节型(byte) 字节型是

jQuery可见性过滤器:hidden和:visibility用法实例

  本文实例讲述了jQuery可见性过滤器:hidden和:visibility用法.分享给大家供大家参考.具体分析如下: :hidden 匹配所有不可见元素,如果使用css的visibility属性让元素不显示但是占位,则不属于hidden了 查找display:none的tr元素,$("tr:hidden") :visible 匹配所有可见元素 查找所有display不为none的元素,$("tr:visible") 例子: ? 1 2 3 4 5 <tr

jQuery可见性过滤器:hidden和:visibility用法实例_jquery

本文实例讲述了jQuery可见性过滤器:hidden和:visibility用法.分享给大家供大家参考.具体分析如下: :hidden匹配所有不可见元素,如果使用css的visibility属性让元素不显示但是占位,则不属于hidden了 查找display:none的tr元素,$("tr:hidden") :visible匹配所有可见元素 查找所有display不为none的元素,$("tr:visible") 例子: <tr id="one&qu

统一建模语言UML轻松入门之综合实例

"例,比也"(<说文>),本次连载将给出一个利用UML进行建模的完整实例,综合应用前面学到的知识,达到"举此以例其余"(元刘壎<隐居通议·欧阳公>)的目的. 在我国十年前ATM(自动取款机)还是一个很新鲜的事物,现在在城市的大街小巷随处可见.我们在日常生活中也经常和ATM打交道.本章我们将以简化的ATM系统为例将前面几章中学到的用例图.类图.顺序图.状态图.活动图及协作图知识运用到此例中. 5.1用例图 参与者"银行储户"

Expression Blend实例中文教程(7)

通过前面文章学习,已经对Blend的开发界面,以及控件有了初步的认识.本文将讲述Blend的一个核 心功能,动画设计.大家也许注意到,从开篇到现在,所有的文章都是属于快速入门,是因为这些文章, 都是我曾经学习的经验和工作中使用到的经验总结出来的.在我个人认为,掌握了这些核心功能也就等于 掌握了Blend的开发方法.在以后开发项目中使用Blend开发工具,这些知识应该足够用了.当然,特殊项 目也需要特殊对待,如果您在项目开发中,有新的Blend开发经验,希望您能够毫不吝啬的分享,在这里 ,我表示深

探讨Java内部类的可见性

在Java中,当生成一个内部类的对象时,此对象与制造它的外部类通过外部类的.this保持着联系,因此该内部类对象可以访问其外部类对象的所有成员,包括private成员. 而该内部类对象对于其他类的对象的访问,遵照常规的访问权限语法,这一点也没有什么特别支持.这里需要探讨的是,外部类以及其他类的对象可以如何访问到某个内部类对象,即内部类的可见性问题. 下面是一个示例程序Out.java,其中包含了4个不同访问权限的内部类(private,default,protected,public),在每个内

《Java安全编码标准》一1.7 并发性、可见性和内存

1.7 并发性.可见性和内存 可以在不同线程之间共享的内存称为共享内存(shared memory)或内存堆(heap memory).本节使用变量(variable)这个名词来代表字段和数组元素[JLS2005].在不同的线程中共享的变量称为共享变量.所有的实例字段.静态字段以及数组元素作为共享变量存储在共享内存中.局部变量.形式方法参数以及异常例程参数是从来不能在线程之间共享的,不会受到内存模型的 影响. 在现代多处理器共享内存的架构下,每个处理器有一个或多个层次的缓存,会定期地与主存储器进

ECshop 迁移到 PHP7版本时遇到的兼容性问题_php实例

在 PHP7 上安装 ECShop V2.7.3时,报错! Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; ECS has a deprecated constructor in /usr/local/nginx/html/ecshop/upload/includes/cls_ecshop.php on line 25   这个报错