简体   繁体   English

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

[英]java thread accessing outer object before it's created

Yes, this is an academic question, I know people will complain that I'm not posting any code but I'm genuinely struck with this question, really don't know where to begin. 是的,这是一个学术问题,我知道人们会抱怨我没有发布任何代码,但我真的很震惊这个问题,真的不知道从哪里开始。 I would really appreciate an explanation and maybe some code example. 我真的很感激解释和一些代码示例。

If an object constructor starts a new thread that executes the method run of an anonymous inner class object, it is possible that this new thread can access its surrounding outer object before it has been fully constructed and its fields fully initialized. 如果对象构造函数启动一个执行匿名内部类对象的方法运行的新线程,则该新线程可能在完全构造并且其字段完全初始化之前可以访问其周围的外部对象。 How would you prevent this from happening? 你会如何防止这种情况发生?

This is called "leaking this". 这被称为“泄漏这个”。 Here you have the code 这里有代码

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

}

Guess what it will (may, since it's a thread) print. 猜猜它会是什么(可能,因为它是一个线程)打印。 I used a final field to stress that the object is accessed before it has been fully initialized (final fields must be definitely assigned after the last line of every constructor) 我使用final字段来强调在完全初始化之前访问对象(必须在每个构造函数的最后一行之后明确赋值

How do you recover 你怎么恢复

You don't want to pass this around when you are in a constructor. 当你在构造函数中时,你不想传递this This also mean you don't want to call non-final virtual methods in the very same class (non-static, non-private), and not using inner classes (anonymous classes are inner classes), that are implicitely linked to the enclosing instance, thus it's as they could access this . 这也意味着您不希望在同一个类(非静态,非私有)中调用非最终虚拟方法,而不使用内部类(匿名类是内部类),这些类隐含地与封闭类相关联实例,因此他们可以访问this

Think about the single-threaded situation first: 首先考虑单线程情况:

Whenever you create an object via new , its constructor is called which (hopefully) initializes the fields of the new object before a reference to this object is returned. 无论何时通过new创建对象,都会调用其构造函数(希望)在返回对此对象的引用之前初始化新对象的字段。 That is, from the point of view of the caller, this new is almost like an atomic operation: 也就是说,从调用者的角度来看,这个new的几乎就像一个原子操作:

Before calling new , there is no object. 在调用new之前,没有对象。 After returning from new , the object exists fully initialized. new返回后,对象存在完全初始化。

So all is good. 一切都很好。


The situation changes slightly when multiple threads come into play. 当多个线程发挥作用时,情况会略有变化。 But we have to read your quote carefully: 但我们必须仔细阅读你的引用:

...has been fully constructed and its fields fully initialized. ......已经完全建成并且其领域已经完全初始化。

The crucial point is fully . 关键点是fully The subject line of your question says "before created", but what is meant here is not before the object has been created, but between object creation and initialization. 你问题的主题是“创建之前”,但这里的含义不是在创建对象之前,而是在对象创建和初始化之间。 In a multi-threaded situation, new can no longer be considered (pseudo-)atomic because of this (time flows from left to right): 在多线程情况下,由于这一点(时间从左向右流动), new不再被视为(伪)原子:

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

So how can Thread2 mess with the object? 那么Thread2如何搞乱对象呢? It would need a reference to that object but since new will only return the object after is both been created and initialized, this should be impossible, right? 它需要对该对象的引用,但由于new只会在创建和初始化之后返回该对象,这应该是不可能的,对吧?

Well, no - there is one way where it's still possible -- namely if Thread 2 is created inside the object's constructor . 嗯,不 - 有一种方法仍然可以 - 即如果在对象的构造函数内创建线程2。 Then the situation would be like this: 然后情况会是这样的:

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

Since Thread2 is created after the object has been created (but before it has been fully initialized), there is already a reference to the object that Thread2 could get a hold of. 由于Thread2是在创建对象之后创建的(但在完全初始化之前),因此已经有一个对Thread2可以获取的对象的引用。 One way is simply if the constructor of Thread2 explicitly takes a reference to the object as a parameter. 一种方法是简单地,如果Thread2的构造函数显式地将对象的引用作为参数。 Another way is by using a non-static inner class of the object for Thread2's run method. 另一种方法是使用Thread2的run方法的对象的非静态内部类。

I do not fully agree with Pablos answer because it heavily depends on your initialization method. 我不完全同意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();
    }
}

When you 当你

  1. place t.start() at the end, the correct data is printed. t.start()放在最后,打印正确的数据。
  2. place t.start() before the sleep command, it will print 0 在sleep命令之前放置t.start() ,它将打印0
  3. remove the sleep command and place t.start() before the assignment it can print 1 (not determinable) 删除sleep命令并将t.start()放在它可以打印的赋值之前1(不可确定)

Play a mind game on 3.) you can say a "tiny" assignment of 1 simple data type will work as expected but if you create a database connection it will not achieve a reliable result. 在3.)上玩心智游戏。你可以说1个简单数据类型的“微小”分配将按预期工作,但如果你创建数据库连接,它将无法获得可靠的结果。

Do not hesitate to raise any question. 不要犹豫提出任何问题。

I would change the title of the question, as threads are not accessing themselves, but the second one to the first one. 我会改变问题的标题,因为线程不是自己访问,而是第二个到第一个。 I mean: You have one thread, creating an object. 我的意思是:你有一个线程,创建一个对象。 Inside the constructor for this object, you declare an anonymous inner class that implements Runnable. 在此对象的构造函数内,您声明了一个实现Runnable的匿名内部类。 In the same constructor of the first thread, you start a new thread to run your anonymous inner class. 在第一个线程的相同构造函数中,您启动一​​个新线程来运行您的匿名内部类。 Thus, you're having two threads. 因此,你有两个线程。 If you want to assure that the new thread doesn't do anything before the constructor is "fully ended", I would use some locks in the constructor. 如果你想确保新的线程在构造函数“完全结束”之前没有做任何事情,我会在构造函数中使用一些锁。 This way, the 2nd thread can be started but will wait until the first thread ends. 这样,第二个线程可以启动,但会等到第一个线程结束。

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

So a situation like this? 那么这样的情况呢?

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

Depending on the actual code used, the behaviour could vary. 根据实际使用的代码,行为可能会有所不同。 This is why constructors should be carefully made so that they don't for example call non-private methods (a subclass could override it, allowing the superclass this to be accessed from a subclass before the superclass is fully initialized). 这就是为什么构造函数要精心制作,使它们例如呼叫非私有方法(一个子类可以重写它,从而使超没有this从一个子类访问之前超完全初始化)。 Although this particular example deals with a single class and a thread, it's related to the reference leaking problem. 虽然这个特定的例子涉及单个类和一个线程,但它与参考泄漏问题有关。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM