Почему Java-конструкторы не могут быть синхронизированы? - PullRequest
63 голосов
/ 03 февраля 2011

Согласно Спецификации языка 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 ]

30 голосов
/ 03 февраля 2011

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

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
       }
    }
}

Обычно считается плохим стилем «выдавать» ваш еще не построенный объект, как этот, поэтому синхронизированный конструктор не нужен.


В некоторых угловых случаях будет полезен синхронизированный конструктор.Вот более реалистичный пример из обсуждения ответа Божо:

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

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

17 голосов
/ 05 августа 2013

Этот вопрос был поднят в дискуссионном списке, используемом авторами параллельного API Java и модели памяти Java.Было дано несколько ответов, в частности Ханс Бём ответил :

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

8 голосов
/ 03 февраля 2011

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

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

5 голосов
/ 04 сентября 2013

Секция модификаторов конструктора в 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.

Таким образом, нет необходимости синхронизировать конструктор.

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

5 голосов
/ 03 февраля 2011

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

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

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

3 голосов
/ 22 января 2013

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

Синхронизированный конструктор исправит эту проблему. Я прав насчет этого?

0 голосов
/ 15 ноября 2018

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

0 голосов
/ 21 октября 2017

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

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

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

0 голосов
/ 13 февраля 2013

Следующий код может достичь ожидаемого результата для синхронизированного конструктора.

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