根据the Java Language Specification,构造函数不能被标记为同步,因为在创建它的线程完成之前,其他线程无法看到正在创建的对象。这看起来有点奇怪,因为在构造对象时我确实可以有另一个线程查看对象:
public class Test {
public Test() {
final Test me = this;
new Thread() {
@Override
public void run() {
// ... Reference 'me,' the object being constructed
}
}.start();
}
}
我知道这是一个非常人为的例子,但理论上似乎有人可以提出一个更现实的案例,即标记构造函数同步将是合法的,以防止像这样的线程的比赛。
我的问题是:Java有没有明确禁止构造函数上的synchronized修饰符的原因?也许我上面的例子是有缺陷的,或者也许没有理由,这是一个任意的设计决定。在任何一种情况下,我都很好奇,很想知道答案。
答案 0 :(得分:30)
如果你真的需要同步其余的构造函数与任何线程无论如何获得对你尚未完全构造的对象的引用,你可以使用synchronized-block:
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.");
}
}
}
我们希望仅在完成SubClass对象的构造后调用doSomethingDangerous()
方法,例如我们只希望“一切正常”输出。但在这种情况下,当您只能编辑SubClass时,您就没有机会实现这一目标。如果构造函数可以同步,它将解决问题。
所以,我们从中学到了什么:如果你的类不是最终的,那就不要像我在超类构造函数中所做的那样做 - 并且不要从你的构造函数中调用你自己的类的任何非final方法。
答案 1 :(得分:16)
在Java并发API和Java内存模型的编写者使用的讨论列表中提出了这个问题。给出了几个答案,特别是Hans Boehm replied:
我们中的一些人(我自己包括IIRC)在Java内存模型审议期间实际上认为应该允许同步构造函数。现在我可以选择任何一种方式。客户端代码不应该使用种族来传播引用,因此它无关紧要。但如果你不相信[你的班级]的客户,我认为同步构造函数可能是有用的。这是最终字段语义背后的大部分原因。 [...]正如大卫所说,你可以使用同步块。
答案 2 :(得分:8)
因为synchronized
保证多个线程不会对相同对象执行操作。当调用构造函数时,您仍然没有该对象。两个线程在逻辑上不可能访问同一对象的构造函数。
在您的示例中,即使新线程调用了一个方法,它也不再是关于构造函数的 - 它是关于正在同步的目标方法。
答案 3 :(得分:5)
在您的示例中,构造函数实际上只从一个线程调用一次。
是的,可以获得对未完全构造的Object的引用(关于双重检查锁定的一些讨论以及为什么它会被破坏揭示这个问题),但是,不是通过第二次调用构造函数。
构造函数上的Syncronized会阻止两个线程同时在同一个Object上调用构造函数,这是不可能的,因为永远不可能在对象实例上调用构造函数两次,周期。
答案 4 :(得分:5)
Constructor Modifiers section明确表示
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.
因此不需要同步构造函数。
此外,建议不要在创建对象之前给出对象引用(this)。可能的模糊情况之一是在创建子类对象时给出对象引用是超类构造函数。
答案 5 :(得分:3)
我认为没有理由禁止构造函数同步。它在多线程应用程序的许多场景中都很有用。如果我正确理解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创建,则实例的引用与线程2共享,线程2调用print()
,则可能发生这种情况。如果线程1的写myInt = 3
与线程2读取相同字段之间没有特殊同步,则不保证线程2看到写入。
同步构造函数可以解决此问题。我是对的吗?
答案 6 :(得分:0)
以下代码可以实现同步构造函数的预期结果。
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();
}
}
答案 7 :(得分:0)
这种同步在某些非常罕见的情况下可能有意义,但我猜,它不值得:
如有疑问,请将其保留。
答案 8 :(得分:0)
请注意,构造函数无法同步-将synchronizedkeyword与构造函数一起使用是语法错误。同步构造函数没有任何意义,因为只有创建对象的线程才可以在构造对象时对其进行访问。