直接从这个网站上,我发现了以下关于创建对象线程安全的描述。
警告:当构造一个将在多个线程之间共享的对象时,请非常小心,确保对该对象的引用不会过早地“泄漏”。例如,假设您想维护一个名为instances的列表,其中包含类的每个实例。您可能会被诱导将以下行添加到构造函数中:
instances.add(this);
但是,此时其他线程可以使用instances访问对象,在对象构造完成之前就访问该对象。
是否有人能用其他词语或更易理解的例子来表达相同的概念?
提前感谢。
直接从这个网站上,我发现了以下关于创建对象线程安全的描述。
警告:当构造一个将在多个线程之间共享的对象时,请非常小心,确保对该对象的引用不会过早地“泄漏”。例如,假设您想维护一个名为instances的列表,其中包含类的每个实例。您可能会被诱导将以下行添加到构造函数中:
instances.add(this);
但是,此时其他线程可以使用instances访问对象,在对象构造完成之前就访问该对象。
是否有人能用其他词语或更易理解的例子来表达相同的概念?
提前感谢。
Let us assume, you have such class:
class Sync {
public Sync(List<Sync> list) {
list.add(this);
// switch
// instance initialization code
}
public void bang() { }
}
and you have two threads (thread #1 and thread #2), both of them have a reference the same List<Sync> list
instance.
Now thread #1 creates a new Sync
instance and as an argument provides a reference to the list
instance:
new Sync(list);
While executing line // switch
in the Sync
constructor there is a context switch and now thread #2 is working.
Thread #2 executes such code:
for(Sync elem : list)
elem.bang();
Thread #2 calls bang()
on the instance created in point 3, but this instance is not ready to be used yet, because the constructor of this instance has not been finished.
以下是您需要的简明示例:
假设有一个名为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
对象。当执行该线程的最后一行代码如下时,listOfHouse.add(this);
上下文被切换(这个对象的引用已经添加到了列表listOfHouse
中,但是对象创建还没有完成),切换到另一个线程“Y”运行,
printsHouses();
如果在它里面!然后printHouses()
会看到一个仍未完全创建的对象,这种不一致性被称为泄漏
。
这里有很多有用的数据,但我想添加更多信息。
当构建一个将在多个线程之间共享的对象时,请务必小心,确保对该对象的引用不会提前“泄漏”。
在构造对象时,您需要确保其他线程无法在它完全构造之前访问此对象。这意味着在构造函数中,您不应该:
static
字段。You might be tempted to add the following line to your constructor:
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
字段本身已同步,所以你不必担心它们。
instances.add(this);
? - Weishi Z线程A正在创建对象A,在对象A的构造函数第一行中间发生了上下文切换。现在线程B正在工作,线程B可以查看对象A(他已经有引用了)。但是对象A还没有完全构建好,因为线程A没有时间完成它。
public clsss MyClass {
public MyClass(List<?> list) {
// some stuff
list.add(this); // self registration
// other stuff
}
}
MyClass
将自己注册到列表中,供其他线程使用。但它在注册后会运行“其他内容”。这意味着如果其他线程在构造函数完成之前开始使用对象,则该对象可能尚未完全创建。
它描述了以下情况:
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);
由于线程调度程序可以在任何时候停止执行线程(甚至在高级指令的一半,例如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();
}
由于在main()
函数中访问a
未同步,因此执行会偶尔失败。
当线程t1
在完成对象A
的构建之前停止执行,并且线程t2
被执行时,就会发生这种情况。这导致线程t2
尝试访问包含nullptr
的unique_ptr<A>
。
你只需要确保即使一个线程没有初始化对象,也不会有其他线程访问它(并获得NullpointerException)。
在这种情况下,它可能会发生在构造函数中(我猜测),但是另一个线程可能会在将其添加到列表和构造函数结束之间访问该对象。