2011-02-02 7 views
55

В соответствии с the Java Language Specification конструкторы не могут быть помечены как синхронизированные, потому что другие потоки не могут видеть создаваемый объект, пока поток, создающий его, не завершит его. Это кажется немного странным, потому что я действительно может иметь другой поток просмотра объекта, пока он строится:Почему нельзя синхронизировать конструкторы Java?

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

Я знаю, что это довольно надуманный пример, но, кажется, в теории, что кто-то мог придумать более реалистичный случай, когда обозначение синхронизированного конструктора было бы законным, чтобы предотвратить гонки с такими потоками.

Мой вопрос заключается в следующем: есть ли причина, по которой Java специально запретит синхронизированный модификатор на конструкторе? Возможно, мой приведенный выше пример ошибочен, или, возможно, нет причин, и это произвольное дизайнерское решение. В любом случае мне действительно любопытно и хотелось бы узнать ответ.

+9

В стороне, настоятельно рекомендуется не допускать «эта» ссылка для выхода до завершения конструктора. –

+1

@Mike Q- Я слышал это раньше, но не совсем понимаю почему. Есть ли какая-то особая причина? Я мог видеть, что Bad Things происходит, если вы дали ссылку на это до того, как закончите инициализацию объекта, но что, если это последнее, что вы делаете в конструкторе? – templatetypedef

+2

это действительно вопрос другого вопроса, но даже если это последнее, что вы делаете в конструкторе, если объект подклассифицирован, то подкласс не завершил построение. Если этот класс является окончательным, и вы не создаете цепочку конструкторов (называете это (...)) и делаете что-то еще после вызова цепи, и это последнее, что вы делаете, это прекрасно. Кроме того, что любое из этих решений может измениться (позже вы можете добавить второй конструктор). – Yishai

ответ

25

Если вам действительно нужна синхронизация остальной части конструкторы против любых потоков, которые так или иначе получает ссылку на ваш пока еще не полностью построенный объект, вы можете использовать синхронизированный-блок:

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, у вас нет шансов на это. Если конструктор может быть синхронизирован, это решит проблему.

Итак, что мы узнаем об этом: никогда не делайте то, что я сделал здесь, в конструкторе суперкласса, если ваш класс не является окончательным - и не вызывайте какие-либо не конечные методы вашего собственного класса из вашего конструктора.

+0

ahh, избили меня :) +1 –

+1

@Paulo, ваш пример очень надуманный. Во-первых, было бы плохой практикой вызывать абстрактный метод из конструктора, но что более важно, если бы ни одна из потоков в подклассе не имела бы * точно * той же проблемы. Так что syncronized добавит что? Поддержка очень маловероятного случая, в то же время вызывающая подкласс, в любом случае должна работать над гораздо более распространенным случаем таким образом, который мог бы работать в обоих случаях. – Yishai

+0

@Yishai: Вы правы, пример не так реалистичен. Но я видел классы, которые публикуют свои собственные объекты в конструкторе для других потоков (не обязательно в том же классе, а не обязательно со сном), а затем у вас могут быть такие проблемы, в которых может помочь синхронизированный конструктор. Конечно, этот метод в моем примере должен охранять себя за вызов из конструктора суперкласса. –

7

Потому что synchronized гарантирует, что действия на одних и тех же объектах не должны выполняться несколькими потоками. И когда конструктор называется, у вас все еще нет объекта. Логически невозможно, чтобы два потока обращались к конструктору того же объекта.

В вашем примере, даже если метод вызван новым потоком, это уже не о конструкторе - речь идет о синхронизируемом целевом методе.

+0

Вы правы, что два потока не могут оба вызвать конструктор, но что, если один поток вызывает конструктор, который затем запускает другой поток, который пытается вызвать другой синхронизированный метод класса? В этом случае наличие синхронизированного конструктора помешает второму потоку вызывать любые синхронизированные методы до тех пор, пока конструктор не завершится. – templatetypedef

+1

больше не о конструкторе, а о методе, о котором идет речь. – Bozho

+3

Они могут быть синхронизированы (это) {} внутри вашего конструктора. Синхронизация по самому конструктору - это вздор. Как вы приобретаете мьютекс для объекта до начала процесса его создания? – Affe

5

В вашем примере конструктор фактически вызывается только один раз из одного потока.

Да, вы можете получить ссылку на неполностью сконструированный объект (некоторые обсуждения вокруг блокировки двойной проверки и почему это ломается, выявить эту проблему), однако, не путем вызова конструктора во второй раз.

Синхронизированный конструктор предотвратит одновременное вызов двух потоков из одного конструктора на одном и том же объекте, и это невозможно, так как никогда невозможно вызвать конструктор экземпляра объекта дважды, период.

+0

Downvoter, по какой-либо причине? – Yishai

+4

Я не спустил вниз, но возможная причина: синхронизация на конструкторе не только позволит избежать двух потоков, вызывающих конструктор того же потока (что в любом случае невозможно), но и избежать вызова синхронизированного метода этого объекта (или ввода синхронизированного блок с этим объектом в качестве параметра) в другом потоке. –

1

Я вижу небольшую причину, чтобы запретить синхронизацию конструкторов.Это было бы полезно во многих сценариях в многопоточных приложениях. Если я правильно понял модель памяти 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, ссылка на экземпляр разделяется с помощью Thread 2, а Thread 2 вызывает print(). Если нет специальной синхронизации между записью myInt = 3 Thread 1 и чтением того же поля по Thread 2, Thread 2 не гарантированно увидит запись.

Синхронизированный конструктор исправит эту проблему. Правильно ли я об этом?

+0

(i) проблема, которую вы описываете, заключается в том, что экземпляр A не опубликован должным образом, и в этом случае его наблюдаемое состояние может быть чем-то действительно (ii) вы можете написать 'synchronized (this) {myInt = 3; } 'в вашем конструкторе для решения проблемы или пометьте' myInt' как final. – assylias

+0

Вы также можете пометить переменную 'myInt' ключевым словом' volatile'. – ponkin

+1

Не обязательно. Если ссылка объекта просочилась в какой-либо другой поток до его полной сборки, тогда другой поток может также вызвать несинхронизированный метод этого объекта, и в этом случае синхронизация конструктора никогда не помогает. Избегание более позднего случая означает, что никакая утечка не должна выполняться, после чего синхронизация конструктора становится ненужной. – lcn

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

Вопрос был поднят в списке обсуждений, используемом авторами Java параллельного API и модели памяти Java. Несколько были даны ответы, в частности, Hans Boehm replied:

Некоторые из нас (включая меня IIRC) фактически утверждал в ходе обсуждения модели памяти Java, что синхронизированные конструкторы должны быть разрешены. Теперь я мог бы пойти в любом случае. Клиентский код не должен использовать расы для передачи ссылки, поэтому это не имеет значения. Но если вы не доверяете клиентам [вашего класса], я думаю, что синхронизированные конструкторы могут быть полезны. И это было большим аргументом в пользу окончательной полевой семантики. [...] Как сказал Дэвид, вы можете использовать синхронизированные блоки.

3

Constructor Modifiers section в 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. 

Так что нет никакой необходимости в конструктор для синхронизации.

Также не рекомендуется указывать ссылку на объект (это) перед созданием объекта. Одна из возможных неоднозначных ситуаций заключалась бы в том, чтобы указать ссылку на объекты - конструктор суперкласса при создании объекта подкласса.

+0

Вы можете синхронизировать что-то еще. – momomo

0

Такая синхронизация может иметь смысл в некоторых очень редких случаях, но я думаю, это просто не стоит:

  • вы всегда можете использовать синхронизированный блок вместо
  • было бы поддерживать кодирование в довольно странный способ
  • на что он должен синхронизироваться? Конструктор - это своего рода статический метод, он работает над объектом, но получает его без него. Поэтому синхронизация по классу также делает (некоторые) смысл!

Если у вас есть сомнения, оставьте это.

Смежные вопросы