以线程安全方式创建对象

时间:2013-05-23 15:27:07

标签: java multithreading concurrency

直接从this网站,我看到了有关创建对象线程安全的以下描述。

  

警告:构建将在其间共享的对象时   线程,要非常小心,对对象的引用不会   过早地“泄漏”。例如,假设您要维护List   调用包含每个类实例的实例。你可能是   试图将以下行添加到构造函数中:

     

instances.add(本);

     

但是其他线程之前可以使用实例来访问对象   对象的构造完成。

是否有人能够用其他词语或其他更易于理解的例子表达相同的概念?

提前致谢。

8 个答案:

答案 0 :(得分:4)

  1. 我们假设你有这样的课程:

    class Sync {
        public Sync(List<Sync> list) {
            list.add(this);
            // switch
            // instance initialization code
        }
    
        public void bang() { }
    }
    
  2. 你有两个线程(线程#1和线程#2),它们都有一个相同的List<Sync> list实例的引用。

  3. 现在,线程#1创建了一个新的Sync实例,并且作为参数提供了对list实例的引用:

    new Sync(list);
    
  4. // switch构造函数中执行行Sync时,有一个上下文切换,现在线程#2正在工作。

  5. 线程#2执行以下代码:

    for(Sync elem : list)
        elem.bang();
    
  6. 线程#2在第3点创建的实例上调用bang(),但此实例尚未准备好使用,因为此实例的构造函数尚未结束。

  7. 因此,

    • 在调用构造函数并将引用传递给在几个线程之间共享的对象时,您必须非常小心
    • 在实现构造函数时,您必须记住所提供的实例可以在几个线程之间共享

答案 1 :(得分:2)

线程A正在创建对象A,在创建对象A的中间(在对象A的构造函数的第一行中)存在上下文切换。现在线程B正在工作,线程B可以查看对象A(他已经参考了)。但是,对象A尚未完全构造,因为线程A没有时间完成它。

答案 2 :(得分:2)

以下是您明确的例子:

假设有一个名为House

的类
class House {
    private static List<House> listOfHouse;
    private name;
    // other properties

    public House(){
        listOfHouse.add(this);
        this.name = "dummy house";
        //do other things
    }

 // other methods

}

和村庄:

class Village {

    public static void printsHouses(){
         for(House house : House.getListOfHouse()){
               System.out.println(house.getName());
         }
    }
}

现在,如果您在线程中创建House,则为“X”。当执行线程刚刚完成波纹管线时,

listOfHouse.add(this); 

上下文被切换(已将此对象的引用添加到列表listOfHouse中,而对象创建尚未完成)到另一个线程,“Y”正在运行,

printsHouses();

在里面!然后printHouses()会看到一个仍然没有完全创建的对象,这种不一致性被称为Leak

答案 3 :(得分:2)

这里有很多好的数据,但我想我会添加更多信息。

  

构造一个将在线程之间共享的对象时,要非常小心,对对象的引用不会过早“泄漏”。

在构建对象时,需要确保其他线程无法访问此对象,然后才能构建该对象。这意味着在构造函数中你不应该,例如:

  • 将对象分配给其他线程可访问的类的static字段。
  • 在构造函数中的对象上启动一个线程,该线程可能会在对象初始化之前开始使用对象中的字段。
  • 将对象发布到集合中,或者通过任何其他机制允许其他线程在对象构建之前查看该对象。
  

您可能想要将以下行添加到构造函数中:

   instances.add(this);

所以类似以下内容是不恰当的:

  public class Foo {
      // multiple threads can use this
      public static List<Foo> instances = new ArrayList<Foo>();
      public Foo() {
         ...
         // this "leaks" this, publishing it to other threads
         instances.add(this);
         ...
         // other initialization stuff
      }
      ...

复杂性的另一个原因是Java编译器/优化器能够对构造函数内部的指令进行重新排序,以便它们在以后发生。这意味着即使你将instances.add(this);作为构造函数的最后一行,这也不足以确保构造函数真正完成。

如果要访问此已发布对象的多个线程,则必须为synchronized。您不需要担心的唯一字段是final字段,保证在构造函数完成时完成构建。 volatile字段本身已同步,因此您无需担心它们。

答案 4 :(得分:1)

我认为以下示例说明了作者想要说的内容:

public clsss MyClass {
    public MyClass(List<?> list) {
        // some stuff 

        list.add(this); // self registration

        // other stuff 
    }
}

MyClass在列表中注册,可供其他线程使用。但它在注册后运行“其他东西”。这意味着如果其他线程在完成构造函数之前开始使用该对象,则该对象可能尚未完全创建。

答案 5 :(得分:0)

它描述了以下情况:

Thread1:
 //we add a reference to this thread
 object.add(thread1Id,this);
 //we start to initialize this thread, but suppose before reaching the next line we switch threads
 this.initialize(); 
Thread2:     
//we are able to get th1, but its not initialized properly so its in an invalid state 
//and hence th1 is not valid
Object th1 = object.get(thread1Id); 

答案 6 :(得分:0)

由于线程调度程序可以随时停止执行线程(甚至是instances.push_back(this)之类的高级指令的中途)并切换到执行不同的线程,如果不这样做,可能会发生意外行为同步对象的并行访问。

请看下面的代码:

#include <vector>
#include <thread>
#include <memory>
#include <iostream>

struct A {
    std::vector<A*> instances;
    A() { instances.push_back(this); }
    void printSize() { std::cout << instances.size() << std::endl; }
};

int main() {
    std::unique_ptr<A> a; // Initialized to nullptr.

    std::thread t1([&a] { a.reset(new A()); }); // Construct new A.
    std::thread t2([&a] { a->printSize(); }); // Use A. This will fail if t1 don't happen to finish before.

    t1.join();
    t2.join();
}

由于访问a中的main() - 函数未同步,执行会偶尔失败。

当在完成对象t1的构造并且执行线程A之前停止执行线程t2时,会发生这种情况。这导致线程t2尝试访问包含unique_ptr<A>的{​​{1}}。

答案 7 :(得分:-1)

你必须确保,即使当一个线程没有初始化Object时,没有Thread会访问它(并获得NullpointerException)。

在这种情况下,它会在构造函数中发生(我想),但是另一个线程可以在它添加到列表和构造函数结尾之间访问该对象。