Java 以线程安全的方式创建对象
我直接从网站上看到了以下关于创建对象线程安全性的描述 警告:在构造将在之间共享的对象时 线程时,请非常小心,以免对对象的引用 过早地“泄漏”。例如,假设您想要维护一个列表 包含类的每个实例的已调用实例。你可能是 尝试将以下行添加到构造函数:Java 以线程安全的方式创建对象,java,multithreading,concurrency,Java,Multithreading,Concurrency,我直接从网站上看到了以下关于创建对象线程安全性的描述 警告:在构造将在之间共享的对象时 线程时,请非常小心,以免对对象的引用 过早地“泄漏”。例如,假设您想要维护一个列表 包含类的每个实例的已调用实例。你可能是 尝试将以下行添加到构造函数: instances.add(this); 实例。添加(此) 但是其他线程可以使用实例来访问之前的对象 对象的构造已完成 有人能用其他词或其他更容易理解的例子来表达相同的概念吗 提前感谢。您只需确保,即使一个线程尚未初始化对象,也不会有线程访问它(并获
instances.add(this);
实例。添加(此)
但是其他线程可以使用实例来访问之前的对象
对象的构造已完成
有人能用其他词或其他更容易理解的例子来表达相同的概念吗
提前感谢。您只需确保,即使一个线程尚未初始化对象,也不会有线程访问它(并获得NullpointerException)
在这种情况下,它会发生在构造函数(假设)中,但是另一个线程可以在它添加到列表和构造函数的末尾之间访问那个对象。 线程A正在创建对象A,在创建对象A的中间(对象A的构造函数的第一行)中有上下文切换。现在线程B正在工作,线程B可以查看对象A(他已经有了引用)。但是,对象A尚未完全构造,因为线程A没有时间完成它。我认为以下示例说明了作者想要说的内容:
public clsss MyClass {
public MyClass(List<?> list) {
// some stuff
list.add(this); // self registration
// other stuff
}
}
公共clsss MyClass{
公共MyClass(列表){
//一些东西
list.add(this);//自注册
//其他东西
}
}
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);
class Sync {
public Sync(List<Sync> list) {
list.add(this);
// switch
// instance initialization code
}
public void bang() { }
}
Sync
构造函数中执行行//switch
时,有一个上下文开关,现在线程2正在工作for(Sync elem : list)
elem.bang();
bang()
,但该实例尚未准备好使用,因为该实例的构造函数尚未完成- 调用构造函数并传递对几个线程之间共享的对象的引用时,必须非常小心
- 在实现构造函数时,您必须记住,提供的实例可以在几个线程之间共享
- 以下是您的明确示例:
比如说,有一个名为
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
}
现在,如果您正在线程中创建房屋
,“X”。当执行线程刚刚完成下面的行时
listOfHouse.add(this);
并将上下文切换(该对象的引用已添加到列表listOfHouse
,而对象创建尚未完成)到另一个运行“Y”的线程
printsHouses();
在里面!然后,
printHouses()
将看到一个尚未完全创建的对象,这种类型的不一致性称为Leak
这里有很多好的数据,但我想我应该添加一些更多的信息
在构造线程之间共享的对象时,要非常小心,不要过早地“泄漏”对该对象的引用
在构造对象时,需要确保其他线程无法访问该对象,然后才能完全构造该对象。这意味着在构造函数中,您不应该,例如:
- 将对象分配给类上其他线程可以访问的
字段静态
- 在构造函数中的对象上启动一个线程,该线程可以在对象的字段被完全初始化之前开始使用这些字段
- 将对象发布到集合中,或通过允许其他线程在构建对象之前查看该对象的任何其他机制
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
}
...
公共类Foo{
//多个线程可以使用此选项
public static List instances=new ArrayList();
公共食物({
...
//这会“泄漏”此信息,并将其发布到其他线程
实例。添加(此);
...
//其他初始化内容
}
...
另一个复杂性是Java编译器/优化器能够对构造函数中的指令重新排序,以便它们在以后发生。这意味着,即使在构造函数的最后一行执行instances.add(This);
,这也不足以确保构造函数真正完成
如果多个线程将要访问这个发布的对象,那么它必须是同步的。你不需要担心的唯一字段是
最终的字段,这些字段保证在构造函数完成时完成构造。volatile
字段本身是同步的,所以你不必工作查看它们。由于线程调度程序可以随时停止线程的执行(即使是在执行像实例这样的高级指令的中途。向后推(此)
)并切换到执行不同的线程,如果不同步对对象的并行访问,可能会发生意外行为
请看下面的代码:
#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{
std::向量实例;
A(){instances.push_back(this);}
void printSize(){std::cout这里有一个非常类似的问题,因此在代码示例中,如何以同步方式完成实例。添加(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();
}