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&

某个地方有人告诉我Java构造函数是同步的,因此在构造过程中不能并发访问它,我想知道:如果我有一个构造函数将对象存储在映射中,而另一个线程在其构造完成之前从该映射中检索它,该线程会一直阻塞直到构造函数完成吗

让我用一些代码来演示:

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();
    }
}