验证并发可见性出现问题

验证代码如下
/**
 * 可用于验证并发可见性问题,预计可能出现出现该线程一直阻塞的状态 验证没有成功 据说是因为client JVM和ServerJVM的问题
 * windows下,默认是CLient JVM
 *
 */
public class test {

    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;
    }

=========================================================网上找的资料:=========================================================

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

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

01class VisibilityThreadextendsThread {
02    privatebooleanstop;
03 
04    publicvoidrun() {
05        inti =0;
06        System.out.println("start loop.");
07        while(!getStop()) {
08            i++;
09        }
10        System.out.println("finish loop,i="+ i);
11    }
12 
13    publicvoidstopIt() {
14        stop =true;
15    }
16 
17    publicbooleangetStop(){
18        returnstop;
19    }
20}
21 
22public classVisibilityTest {
23    publicstaticvoidmain(String[] args)throwsException {
24        VisibilityThread v =newVisibilityThread();
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:

01public classInterruptedVisibilityTest {
02    publicvoidthink() {
03        System.out.println("新线程正在执行");
04        while(true) {
05            if(checkInterruptedStatus())break;
06        }
07        System.out.println("新线程退出循环");
08    }
09 
10    privatebooleancheckInterruptedStatus() {
11        returnThread.currentThread().isInterrupted();
12    }
13 
14    publicstaticvoidmain(String[] args)throwsException {
15        finalInterruptedVisibilityTest test =newInterruptedVisibilityTest();
16        Thread thinkerThread =newThread("Thinker") {
17            publicvoidrun() {
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的最佳优化模式。

扩展阅读:

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

====================================================JVM client模式和Server模式的区别====================================================

2010-09-27 15:17 Harry blog.163.com 我要评论(0) 字号: T | T
一键收藏,随时查看,分享好友!

JVM Server模式与client模式启动,最主要的差别在于:-Server模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升。

AD:2013云计算架构师峰会超低价抢票中

这里向大家描述一下JVM client模式和Server模式两者的区别和联系,JVM如果不显式指定是-Server模式还是-client模式,JVM能够根据下列原则进行自动判断(适用于Java5版本或者Java以上版本)。

JVM client模式和Server模式

JVM Server模式与client模式启动,最主要的差别在于:-Server模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升。JVM如果不显式指定是-Server模式还是-client模式,JVM能够根据下列原则进行自动判断(适用于Java5版本或者Java以上版本)。

前段时间有个同事给我发了个java跟c++性能比较的文章,其中有个对比图引起了我的兴趣,意外的是,我感兴趣的不是java和c++的对比,而是java -Server模式和java -client模式的对比。从来没想到两者间的性能有如此巨大的差别。而在后来自己的亲身测试中发现确实如此。

下面是我看到的那个对比图:

图中最显著的就是JVM client模式和Server模式关于method call的对比,那个差别不是一般的大,在后来的测试中发现,相差至少有10倍。

下面是另外两个对比图:

 

JVM工作在Server模式可以大大提高性能,但应用的启动会比client模式慢大概10%。当该参数不指定时,虚拟机启动检测主机是否为服务器,如果是,则以Server模式启动,否则以client模式启动,J2SE5.0检测的根据是至少2个CPU和最低2GB内存。

当JVM用于启动GUI界面的交互应用时适合于使用client模式,当JVM用于运行服务器后台程序时建议用Server模式。
JVM在client模式默认-Xms是1M,-Xmx是64M;JVM在Server模式默认-Xms是128M,-Xmx是1024M。我们可以通过运行:java -version来查看jvm默认工作在什么模式。

重新测试后,一下方法可行,但是调用yield和system.out.print都会使得测试无效,原因未知。
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package test;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Administrator
 */
public class test {

    public static void main(String[] args) {
        tt v = new tt();
        v.start();
        v.num = 48;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            Logger.getLogger(test.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println("----------1--------" + v.isReady);
        v.setReady();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            Logger.getLogger(test.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println("----------2--------" + v.isReady);
    }

    public static class tt extends Thread {

        public boolean isReady = false;
        public int num = 0;

        public void setReady() {
            isReady = true;
        }

        public void run() {
//            while (!isReady) {
//                Thread.yield();//调用此方法会导致isready可见
//            }
            int i = 0;
            System.out.println("start loop.");
            while (!isReady) {
                i++;
//                System.out.println(i);调用此方法会导致isready可见
            }
            System.out.println(num);
        }
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值