Java中的构造函数同步
某个地方有人告诉我Java构造函数是同步的,因此在构造过程中不能并发访问它,我想知道:如果我有一个构造函数将对象存储在映射中,而另一个线程在其构造完成之前从该映射中检索它,该线程会一直阻塞直到构造函数完成吗 让我用一些代码来演示:Java中的构造函数同步,java,multithreading,concurrency,constructor,Java,Multithreading,Concurrency,Constructor,某个地方有人告诉我Java构造函数是同步的,因此在构造过程中不能并发访问它,我想知道:如果我有一个构造函数将对象存储在映射中,而另一个线程在其构造完成之前从该映射中检索它,该线程会一直阻塞直到构造函数完成吗 让我用一些代码来演示: public class Test { private static final Map<Integer, Test> testsById = Collections.synchronizedMap(new HashMap&
public class Test {
private static final Map<Integer, Test> testsById =
Collections.synchronizedMap(new HashMap<>());
private static final AtomicInteger atomicIdGenerator = new AtomicInteger();
private final int id;
public Test() {
this.id = atomicIdGenerator.getAndIncrement();
testsById.put(this.id, this);
// Some lengthy operation to fully initialize this object
}
public static Test getTestById(int id) {
return testsById.get(id);
}
}
我只是想澄清Java中构造函数同步的进展情况,以及这样的代码是否有问题。我最近看到过这样的代码,它们没有使用静态工厂方法,而是这样做的,我想知道在多线程系统中这有多危险(或安全)
某个地方有人告诉我,Java构造函数是同步的,所以在构造过程中不能并发访问它
事实当然并非如此。不存在与构造函数的隐含同步。不仅可以同时发生多个构造函数,而且还可以通过以下方式获得并发性问题:例如,在构造函数内部分叉一个线程,并引用正在构造的this
如果我有一个将对象存储在映射中的构造函数,而另一个线程在其构造完成之前从该映射中检索对象,那么该线程会一直阻塞直到构造函数完成吗
不会的
线程化应用程序中构造函数的一个大问题是,在Java内存模型下,编译器有权对构造函数内部的操作进行重新排序,以便在(所有事情)创建对象引用并完成构造函数之后进行操作<代码>最终字段将保证在构造函数完成时完全初始化,而不是其他“正常”字段
在您的情况下,由于您将测试放入同步映射,然后继续进行初始化,如@Tim所述,这将允许其他线程以可能的半初始化状态获取对象。一种解决方案是使用static
方法创建对象:
private Test() {
this.id = atomicIdGenerator.getAndIncrement();
// Some lengthy operation to fully initialize this object
}
public static Test createTest() {
Test test = new Test();
// this put to a synchronized map forces a happens-before of Test constructor
testsById.put(test.id, test);
return test;
}
我的示例代码之所以有效,是因为您正在处理一个同步映射,它调用synchronized
,以确保测试
构造函数已完成并已进行内存同步
在您的示例中,最大的问题是“之前发生”保证(构造函数可能在Test
放入映射之前无法完成)和内存同步(构造线程和获取线程可能会看到Test
实例的不同内存)。如果将put
移动到构造函数之外,则这两个对象都由同步映射处理。无论它是synchronized
上的哪个对象,都可以保证构造函数在放入映射之前已经完成,并且内存已经同步
我相信如果调用testsById.put(this.id,this)代码>在构造函数的最后,您实际上可能还可以,但是这不是一个好的形式,至少需要仔细的注释/文档。如果类是子类的,并且在super()
之后在子类中完成初始化,则这不会解决问题。我展示的静态
解决方案是一个更好的模式。您得到的信息是错误的。您所描述的内容实际上被称为不正确的发布,并在Java并发实践手册中详细讨论
因此,是的,另一个线程可以获取对对象的引用,并在完成初始化之前开始尝试使用它。但是等等,更糟糕的是,想想这个答案:…基本上,可以对引用赋值和构造函数完成进行重新排序。在引用的示例中,一个线程可以在新实例上分配h=new Holder(i)
,另一个线程调用h.assertSanity()
,时间正好可以为n
成员(在Holder
的构造函数中分配)获取两个不同的值。这是不安全的。JVM中没有额外的同步。您可以这样做:
public class Test {
private final Object lock = new Object();
public Test() {
synchronized (lock) {
// your improper object reference publication
// long initialization
}
}
public void doSomething() {
synchronized (lock) {
// do something
}
}
}
构造函数与其他方法一样,没有额外的同步(除了处理final
字段)
如果以后发布此
,代码将起作用
public Test()
{
// Some lengthy operation to fully initialize this object
this.id = atomicIdGenerator.getAndIncrement();
testsById.put(this.id, this);
}
某个地方有人告诉我Java构造函数是同步的
“某处有人”被严重误导了。构造函数不同步。证明:
public class A
{
public A() throws InterruptedException
{
wait();
}
public static void main(String[] args) throws Exception
{
A a = new A();
}
}
此代码在调用wait()
时抛出java.lang.IllegalMonitorStateException
。如果存在有效的同步,则不会
这根本没有道理。它们不需要同步。构造函数只能在new(),
之后调用,根据定义,每次调用new()
都会返回不同的值。因此,构造函数被两个线程同时调用的可能性为零,且值相同this
。因此,不需要同步构造函数
如果我有一个将对象存储在映射中的构造函数,而另一个线程在其构造完成之前从该映射中检索对象,那么该线程会一直阻塞直到构造函数完成吗
不,为什么会这样?谁来阻止它?让“this”从构造函数中逃逸是一种糟糕的做法:它允许其他线程访问仍在构造中的对象。虽然这个问题得到了回答,但粘贴在问题中的代码没有遵循安全构造技术,因为它允许此引用从构造函数中逃逸,我想与大家分享Brian Goetz在中给出的一个漂亮的解释。我知道可以同时调用多个构造函数,我不是指类级同步,而是obj
public class A
{
public A() throws InterruptedException
{
wait();
}
public static void main(String[] args) throws Exception
{
A a = new A();
}
}