为什么不能同步Java构造函数?

根据Java语言规范 ,构造函数不能被标记为同步的,因为其他线程在创build它的线程完成之前不能看到正在创build的对象。 这似乎有点奇怪,因为我确实可以让另一个线程在构造时查看对象:

public class Test { public Test() { final Test me = this; new Thread() { @Override public void run() { // ... Reference 'me,' the object being constructed } }.start(); } } 

我知道这是一个非常人为的例子,但从理论上来说,有人可能会想出一个更现实的情况,即标记同步的构造函数是合法的,以防止像这样的线程的比赛。

我的问题是:是否有一个原因,Java会明确禁止在构造函数的同步修饰符? 也许我上面的例子是有缺陷的,也许真的没有理由,这是一个任意的devise决定。 无论哪种情况,我都很好奇,很想知道答案。

如果你确实需要同步其他的构造函数,那么你可以使用一个synchronized块:

 public class Test { public Test() { final Test me = this; synchronized(this) { new Thread() { @Override public void run() { // ... Reference 'me,' the object being constructed synchronized(me) { // do something dangerous with 'me'. } } }.start(); // do something dangerous with this } } } 

通常认为不好的风格是“发出”你尚未构造的对象,所以一个同步的构造函数是不必要的。


在某些情况下,同步的构造函数会很有用。 从Bozho的回答的讨论中,这是一个更现实的例子:

 public abstract class SuperClass { public SuperClass() { new Thread("evil") { public void run() { doSomethingDangerous(); }}).start(); try { Thread.sleep(5000); } catch(InterruptedException ex) { /* ignore */ } } public abstract void doSomethingDangerous(); } public class SubClass extends SuperClass { int number; public SubClass () { super(); number = 2; } public synchronized void doSomethingDangerous() { if(number == 2) { System.out.println("everything OK"); } else { System.out.println("we have a problem."); } } } 

我们希望doSomethingDangerous()方法只在我们的SubClass对象的构造完成后调用,例如我们只想要“一切OK”输出。 但在这种情况下,当你只能编辑你的子类时,你没有机会实现这个。 如果构造函数可以同步,就可以解决问题。

所以,我们了解到这一点:不要像在超类构造函数中那样做,如果你的类不是最终的 – 也不要在你的构造函数中调用你自己类的非final方法。

在Java并发API和Java存储器模型的编写者使用的讨论列表上提出了这个问题。 给出了几个答案,特别是Hans Boehm回答说 :

我们中的一些人(我自己包括IIRC)实际上在Java内存模型审议中争辩说应该允许同步的构造函数。 现在我可以去任何一个方面。 客户端代码不应该使用种族来传递引用,所以它不应该。 但是如果你不信任你的类的客户,我认为同步的构造函数可能是有用的。 这就是最终字段语义背后的原因。 正如David所说,你可以使用同步块。

因为synchronized保证对同一对象的操作不被多个线程执行。 而当构造函数被调用时,你还没有这个对象。 两个线程访问相同对象的构造函数在逻辑上是不可能的。

在你的例子中,即使一个方法被新线程调用,它不再是关于构造函数 – 它是关于目标方法被同步或不是。

在你的例子中,构造函数实际上只是从一个线程调用一次。

是的,可以得到一个不完全构造的对象的引用(一些关于双重​​检查locking的讨论,以及为什么它会被破坏来揭示这个问题),然而,不是通过第二次调用构造函数。

在构造函数上同步会阻止两个线程同时调用同一个对象的构造函数,这是不可能的,因为永远不可能两次调用对象实例的构造函数。

JLS中的构造器修饰符部分清楚地表明

 There is no practical need for a constructor to be synchronized, because it would lock the object under construction, which is normally not made available to other threads until all constructors for the object have completed their work. 

所以不需要构造函数被同步。

也不build议在创build对象之前发出对象引用(this)。 其中一个可能的不明确的情况是,当创build子类对象时,引用对象是超类的构造函数。

我没有理由禁止构造函数同步。 这在multithreading应用程序的许多场景中将是有用的。 如果我正确理解了Java内存模型(我读了http://jeremymanson.blogspot.se/2008/11/what-volatile-means-in-java.html ),下面这个简单的类可以从同步的构造函数中获益。

 public class A { private int myInt; public /*synchronized*/ A() { myInt = 3; } public synchronized void print() { System.out.println(myInt); } } 

从理论上讲,我相信print()打电话可能打印“0”。 如果A的实例由线程1创build,实例的引用与线程2共享,线程2调用print() ,则可能发生这种情况。 如果线程1的写入myInt = 3和线程2读取同一字段之间没有特别的同步,则线程2不能保证看到写入。

一个同步的构造函数可以解决这个问题。 我对吗?

以下代码可以实现同步构造函数的预期结果。

 public class SynchronisedConstructor implements Runnable { private int myInt; /*synchronized*/ static { System.out.println("Within static block"); } public SynchronisedConstructor(){ super(); synchronized(this){ System.out.println("Within sync block in constructor"); myInt = 3; } } @Override public void run() { print(); } public synchronized void print() { System.out.println(Thread.currentThread().getName()); System.out.println(myInt); } public static void main(String[] args) { SynchronisedConstructor sc = new SynchronisedConstructor(); Thread t1 = new Thread(sc); t1.setName("t1"); Thread t2 = new Thread(sc); t2.setName("t2"); t1.start(); t2.start(); } } 

在一些非常罕见的情况下,这样的同步可能是有意义的,但我想,这是不值得的:

  • 您可以始终使用同步块
  • 它会以一种非常奇怪的方式支持编码
  • 它应该同步什么? 构造函数是一种静态方法,它在一个对象上工作,但没有它就被调用。 所以在课堂上同步也使(一些)感觉!

如有疑问,请将其保留。