Java中的构造函数同步

有人告诉我,Java构造函数是同步的,因此在构造期间无法同时访问它,我想知道:如果我有一个构造函数将对象存储在一个映射中,另一个线程在构造之前从该映射中检索它完成后,该线程会阻塞直到构造函数完成吗?

让我演示一些代码:

public class Test { private static final Map 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); } } 

假设put / get是地图上唯一的操作,所以我不会通过迭代之类的东西获得CME,并试图忽略其他明显的缺陷。

我想知道的是,如果另一个线程(显然不是那个构造对象的线程)试图使用getTestById访问该对象并在其上调用某些东西,它会阻塞吗? 换一种说法:

 Test test = getTestById(someId); test.doSomething(); // Does this line block until the constructor is done? 

我只想弄清楚构造函数同步在Java中走了多远,以及这样的代码是否有问题。 我最近看过像这样的代码,而不是使用静态工厂方法,我想知道这在multithreading系统中有多危险(或安全)。

有人告诉我,Java构造函数是同步的,因此在构造期间不能同时访问它

当然不是这种情况。 与构造函数没有隐含的同步。 多个构造函数不仅可以同时发生,而且可以通过例如在构造函数内部分配一个线程来获得并发性问题,并引用this构造函数。

如果我有一个构造函数将对象存储在一个映射中,而另一个线程在构造完成之前从该映射中检索它,那么该线程是否会阻塞直到构造函数完成?

不,不会。

线程应用程序中构造函数的一个大问题是,编译器在Java内存模型下具有对构造函数内部操作进行重新排序的权限,以便创建对象引用(构造函数完成)之后进行。 final字段将保证在构造函数完成时完全初始化,而不是其他“正常”字段。

在您的情况下,由于您将Test放入synchronized-map 然后继续进行初始化,如@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 will force the happens-before of the Test constructor testsById.put(test.id, test); return test; } 

我的示例代码有效,因为您正在处理synchronized-map,它会调用synchronized ,以确保Test构造函数已完成并已进行内存同步。

您的示例中的大问题是“发生在之前”保证(构造函数可能在Test放入映射之前未完成)和内存同步(构造线程和获取线程可能会看到Test实例的不同内存) 。 如果将put移到构造函数外部,则两者都由synchronized-map处理。 它被synchronized到什么对象以保证构造函数在放入映射并且内存已经同步之前已经完成并不重要。

我相信如果你调用了testsById.put(this.id, this); 在你的构造函数的最后,你可能在实践中没问题,但这不是一个好的forms,至少需要仔细的评论/文档。 如果类是子类并且在super()之后在子类中完成初始化,这将无法解决问题。 我展示的static解决方案是一个更好的模式。

你被误导了。 您所描述的内容实际上被称为不正确的发布,并在Java Concurrency In Practice一书中进行了详细讨论。

所以,是的,另一个线程可以获得对象的引用,并在完成初始化之前开始尝试使用它。 但等等,考虑到这个答案会变得更糟: https : //stackoverflow.com/a/2624784/122207 …基本上可以重新排序引用赋值和构造函数完成。 在引用的示例中,一个线程可以在新实例上分配h = new Holder(i)和另一个线程调用h.assertSanity() ,其时间恰好为在Holder的构造函数中分配的n成员获取两个不同的值。 。

有人告诉我,Java构造函数是同步的

“有人在某处”被严重误导。 构造函数不同步。 certificate:

 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(),并且根据定义,每次调用new()返回不同的值。 因此,两个线程同时使用相同的值调用构造函数是不可能的。 因此不需要构造函数的同步。

如果我有一个构造函数将对象存储在一个映射中,而另一个线程在构造完成之前从该映射中检索它,那么该线程是否会阻塞直到构造函数完成?

不,为什么会这样呢? 谁会阻止它? 让’this’从这样的构造函数中逃脱是不好的做法:它允许其他线程访问仍在构造中的对象。

构造函数就像其他方法一样,没有额外的同步(处理final字段除外)。

如果稍后发布,代码将起作用

 public Test() { // Some lengthy operation to fully initialize this object this.id = atomicIdGenerator.getAndIncrement(); testsById.put(this.id, this); } 

虽然这个问题得到了解答但是粘贴的代码并没有遵循安全的构造技术,因为它允许这个引用从构造函数中逃脱 ,我想分享Brian Goetz在文章中提出的一个漂亮的解释:“Java理论与实践:安全构建技术“在IBM developerWorks网站上 。

这是不安全的。 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 } } }