Подтвердить что ты не робот

Почему нельзя синхронизировать конструкторы Java?

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

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

Раздел модификаторов конструктора в 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.

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

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

Ответ 6

Я вижу небольшую причину, чтобы запретить синхронизацию конструкторов. Это было бы полезно во многих сценариях в многопоточных приложениях. Если я правильно понял модель памяти 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 не гарантированно увидит запись.

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

Ответ 7

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

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

Ответ 8

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

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

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