`
须等待
  • 浏览: 210805 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

神奇的线程安全问题

    博客分类:
  • Java
阅读更多

    读完了《Java并发编程实践》这本书以后,以为对一般的线程安全问题有一个大概的理解,但是今天遇到的这个问题着实非常神奇,在书中也没有被提到过,这里特别记录下来。

 

public class Test {
    public void test1(O o) throws Exception {
        System.out.println("begin o:" + o.getId() + " " + o.getName());
        if (o.getId() == 1) {
            Thread.sleep(10000);
            System.out.println("o1:" + o.getId() + " " + o.getName());
        }

        if (o.getId() == 2) {
            System.out.println("o2:" + o.getId() + " " + o.getName());
        }
        System.out.println("end o:" + o.getId() + " " + o.getName());
    }

    public static void main(String[] args) throws Exception {

        final O o1 = new O(1, "o1");
        Thread t1 = new Thread() {
            @Override
            public void run() {
                try {
                    Test test = new Test();
                    test.test1(o1);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                try {
                    o1.setId(2);
                    o1.setName("o2");
                    Test test = new Test();
                    test.test1(o1);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        Thread.sleep(10);
        t2.start();

    }
}

class O {

    public O(int id, String name) {
        super();
        this.id = id;
        this.name = name;
    }

    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 

    两个线程先后启动,传入的是不同状态的同一对象。在test1方法的第一条打印中可以看出来,参数对象的状态是不同的,但是当第一个线程在第一个判断处休眠第二个线程开始执行以后,第一个线程打印出来的状态就和之前不一致了,其状态和第二个参数对象的状态一样。按照理论,方法执行以后,JVM会在独立的线程中压入一个方法栈,栈中会包含一个方法执行所需要的所有元素(包括代码和参数),这里为什么会出现方法执行到一半的时候参数状态改变呢?

 

    推测:因为方法栈中压入的是一个对象的指针,而不是整个对象,所有当对象的状态被改变的时候还是可以第一时间被独立线程所察觉,所以尽管已经进入了方法栈,还是会引起对象状态的改变。

 

    以后在处理多线程的问题时,必须要所用无状态Immutable的VO对象。这样才能保证线程安全

0
1
分享到:
评论
3 楼 须等待 2012-11-10  
beiyeren 写道
zfc827 写道
看来你还没有理解线程安全的基本概念啊。
判断一个对象线程安全与否只有一个标准,即此对象在不同的上下文中,是否仍指向同一个对象。
关于这点也很好把握,由于线程在执行一个方法时,会创建自己的堆栈,此堆栈对别的线程而言是透明的。
比如你上面的代码中,run方法中有Test test = new Test();
那么开启的线程会在自己的线程堆栈中创建test这个实例,有多少个线程执行,就会有多少个实例,这些实例之间互不影响。
要引发线程安全问题,只有一个方法,即在同一个JVM进程中,对象实例只存在一个,多个线程对此实例进行操作的场景。
在你上面的代码中,o1对象是在main方法中被实例化,由于main方法是static的,所以o1也是static的,在下面两个线程上下文中引用的都是同一对象。


楼主还可以多看看虚拟机规范,会有帮助的。很多事情,要有理论支撑,不能全靠认为。


嗯,你说得对,这点确实应该是属于虚拟机规范的,我犯的这个错误最主要的原因是太迷信方法栈参数的线程安全性,完全不会想到原来对象成为参数入栈之后其仍然存在线程不安全的情况,现在看来入栈的只是指针不是整个对象,从这点看来方法参数也不一定完全就是安全的,确实应该再认真研究一下虚拟机规范
2 楼 beiyeren 2012-11-10  
zfc827 写道
看来你还没有理解线程安全的基本概念啊。
判断一个对象线程安全与否只有一个标准,即此对象在不同的上下文中,是否仍指向同一个对象。
关于这点也很好把握,由于线程在执行一个方法时,会创建自己的堆栈,此堆栈对别的线程而言是透明的。
比如你上面的代码中,run方法中有Test test = new Test();
那么开启的线程会在自己的线程堆栈中创建test这个实例,有多少个线程执行,就会有多少个实例,这些实例之间互不影响。
要引发线程安全问题,只有一个方法,即在同一个JVM进程中,对象实例只存在一个,多个线程对此实例进行操作的场景。
在你上面的代码中,o1对象是在main方法中被实例化,由于main方法是static的,所以o1也是static的,在下面两个线程上下文中引用的都是同一对象。


楼主还可以多看看虚拟机规范,会有帮助的。很多事情,要有理论支撑,不能全靠认为。
1 楼 zfc827 2012-11-10  
看来你还没有理解线程安全的基本概念啊。
判断一个对象线程安全与否只有一个标准,即此对象在不同的上下文中,是否仍指向同一个对象。
关于这点也很好把握,由于线程在执行一个方法时,会创建自己的堆栈,此堆栈对别的线程而言是透明的。
比如你上面的代码中,run方法中有Test test = new Test();
那么开启的线程会在自己的线程堆栈中创建test这个实例,有多少个线程执行,就会有多少个实例,这些实例之间互不影响。
要引发线程安全问题,只有一个方法,即在同一个JVM进程中,对象实例只存在一个,多个线程对此实例进行操作的场景。
在你上面的代码中,o1对象是在main方法中被实例化,由于main方法是static的,所以o1也是static的,在下面两个线程上下文中引用的都是同一对象。

相关推荐

Global site tag (gtag.js) - Google Analytics