java线程在创建之前访问外部对象

时间:2014-04-15 08:34:12

标签: java multithreading inner-classes anonymous

是的,这是一个学术问题,我知道人们会抱怨我没有发布任何代码 但我真的对这个问题感到震惊,真的不知道从哪里开始。我真的很感激解释,也许还有一些代码示例。

  

如果对象构造函数启动了执行该方法的新线程   运行一个匿名的内部类对象,有可能是这个新的   线程可以在它之前访问它周围的外部对象   完全构建并完全初始化其字段。你好吗?   防止这种情况发生?

5 个答案:

答案 0 :(得分:20)

这被称为“泄漏这个”。这里有代码

public class Test {

  // this is guaranteed to be initialized after the constructor
  private final int val;

  public Test(int v) {
    new Thread(new Runnable() {
      @Override public void run() {
        System.out.println("Val is " + val);
      }
    }).start();
    this.val = v;
  }

}

猜猜它会是什么(可能,因为它是一个线程)打印。我使用final字段强调对象在完全初始化之前被访问(最终字段必须在每个构造函数的最后一行之后明确分配

如何恢复

当你在构造函数中时,你不想传递this。这也意味着您不希望在同一个类(非静态,非私有)中调用非最终虚拟方法,而不使用内部类(匿名类是内部类),这些类隐含地与封闭类相关联实例,因此他们可以访问this

答案 1 :(得分:10)

首先考虑单线程情况:

每当通过new创建对象时,都会调用其构造函数(希望)在返回对此对象的引用之前初始化新对象的字段。也就是说,从调用者的角度来看,这个new几乎就像一个原子操作:

在致电new之前,没有任何对象。从new返回后,对象存在完全初始化。

所以一切都很好。


当多个线程发挥作用时,情况会略有变化。但我们必须仔细阅读你的引用:

  

...已经完全构建并且其字段已完全初始化。

关键点是fully。您的问题的主题行在创建"之前表示"但这里的含义不是在创建对象之前,而是在对象创建和初始化之间。在多线程情况下,由于这一点(时间从左向右流动),new不再被视为(伪)原子:

Thread1 --> create object --> initialize object --> return from `new`
                           ^
                           |
                           | (messing with the object)
Thread2 ------------------/

那么Thread2如何搞乱对象呢?它需要对该对象的引用,但由于new只会在创建和初始化之后返回对象,所以这应该是不可能的,对吗?

嗯,不 - 有一种方法仍然可以 - 即如果在对象的构造函数中创建了线程2。然后情况就是这样:

Thread1 --> create object --> create Thread2 --> initialize object --> return from `new`
                                      |       ^
                                      |       |
                                      |       | (messing with the object)
                                       \-----/

由于在创建了对象之后创建了(但在它完全初始化之前),因此已经有一个对Thread2可以获取的对象的引用。一种方法是简单地,如果Thread2的构造函数显式地将对象的引用作为参数。另一种方法是使用Thread2的run方法的对象的非静态内部类。

答案 2 :(得分:2)

我不完全同意Pablos的回答,因为它在很大程度上取决于你的初始化方法。

public class ThreadQuestion {

    public volatile int number = 0;

    public static void main(String[] args) {
        ThreadQuestion q = new ThreadQuestion();
    }

    public ThreadQuestion() {
        Thread t = new Thread(new Runnable() {

        @Override
        public void run() {
            System.out.println(number);             
        }
        });

        try {
        Thread.sleep(500);
        } catch(Exception e) {
            e.printStackTrace();
        }
        number = 1;     
        t.start();
    }
}

当你

  1. t.start()放在最后,打印正确的数据。
  2. 在sleep命令之前放置t.start(),它将打印0
  3. 删除睡眠命令并将t.start()放置在可以打印1的分配之前(不可确定)
  4. 在3.)上玩一个心灵游戏。你可以说一个小小的" 1个简单数据类型的赋值将按预期工作,但如果您创建数据库连接,则无法获得可靠的结果。

    不要犹豫提出任何问题。

答案 3 :(得分:2)

我会改变问题的标题,因为线程不是自己访问,而是第二个访问第一个。我的意思是: 你有一个线程,创建一个对象。 在此对象的构造函数内,您声明了一个实现Runnable的匿名内部类。 在第一个线程的相同构造函数中,您启动一​​个新线程来运行您的匿名内部类。 因此,你有两个线程。如果你想确保新的线程在构造函数完全结束之前没有做任何事情,那么我会在构造函数中使用一些锁。这样,第二个线程可以启动,但会等到第一个线程结束。

public class A {
    int final number;

    A() {
        new Thread(
            new Runnable() {
                public void run() {
                    System.out.pritnln("Number: " + number);
                }
        }).start();
    number = 2;
    }
}

答案 4 :(得分:1)

这样的情况呢?

public class MyClass  {
    private Object something;
    public MyClass() {
        new Thread() {
            public void run() {
                something = new Object();
            }
        }.start();
    }
}

根据使用的实际代码,行为可能会有所不同。这就是为什么构造函数应该仔细制作,以便他们不必调用非私有方法(子类可以覆盖它,允许在超类完全初始化之前从子类访问超类this。 )。虽然这个特定的例子涉及单个类和一个线程,但它与参考泄漏问题有关。