developer tip

Java 생성자를 동기화 할 수없는 이유는 무엇입니까?

copycodes 2020. 11. 25. 08:06
반응형

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가 생성자에서 동기화 된 수정자를 특별히 허용하지 않는 이유가 있습니까? 아마도 위의 예에 결함이 있거나 실제로 이유가 없으며 임의의 디자인 결정일 수 있습니다. 어느 경우 든 저는 정말 궁금해서 답을 알고 싶습니다.


아직 생성되지 않은 객체에 대한 참조를 얻는 스레드와 나머지 생성자의 동기화가 정말로 필요한 경우 동기화 된 블록을 사용할 수 있습니다.

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 객체의 생성이 완료된 후에 만 ​​메소드가 호출되기를 원합니다. 예를 들어, "everything OK"출력 만 원합니다. 그러나이 경우 하위 클래스 만 편집 할 수있는 경우이를 달성 할 기회가 없습니다. 생성자가 동기화 될 수 있다면 문제가 해결 될 것입니다.

그래서 우리가 이것에 대해 배운 것은 : 만약 당신의 클래스가 final이 아니라면, 여기 슈퍼 클래스 생성자에서했던 것과 같은 것을 절대하지 마십시오. 그리고 생성자로부터 당신 자신의 클래스의 최종이 아닌 메서드를 호출하지 마십시오.


Java 동시 API 및 Java 메모리 모델 작성자가 사용하는 토론 목록에서 문제가 제기되었습니다. 특히 Hans Boehm 은 다음과 같이 대답했습니다 .

우리 중 일부 (내가 IIRC를 포함)는 실제로 Java 메모리 모델 심의 중에 동기화 된 생성자가 허용되어야한다고 주장했습니다. 이제 어느 쪽이든 갈 수 있습니다. 클라이언트 코드는 참조를 전달하기 위해 레이스를 사용하지 않아야하므로 중요하지 않습니다. 하지만 [귀하의 클래스] 클라이언트를 신뢰하지 않는다면 동기화 된 생성자가 유용 할 수 있다고 생각합니다. 그리고 그것은 최종 필드 의미론 뒤에있는 많은 이유였습니다. [...] David가 말했듯이 동기화 된 블록을 사용할 수 있습니다.


때문에 synchronized같은 객체에 대한 작업이 여러 스레드에 의해 수행되지 않는 것을 보장한다. 그리고 생성자가 호출 될 때 여전히 객체가 없습니다. 두 스레드가 동일한 객체의 생성자에 액세스하는 것은 논리적으로 불가능합니다.

귀하의 예제에서 메서드가 새 스레드에 의해 호출 되더라도 더 이상 생성자에 관한 것이 아닙니다. 동기화되는 대상 메서드에 관한 것입니다.


귀하의 예제에서 생성자는 실제로 한 스레드에서 한 번만 호출됩니다.

예, 생성자를 두 번 호출하는 것이 아니라 불완전하게 구성된 객체에 대한 참조를 얻을 수 있습니다 (이중 검사 잠금에 대한 일부 토론과이 문제가 깨진 이유가이 문제를 나타냄).

생성자에서 동기화하면 두 스레드가 동일한 Object에서 생성자를 동시에 호출하는 것을 방지 할 수 있으며, 개체 인스턴스에서 생성자를 두 번 (기간) 호출 할 수 없기 때문에 불가능합니다.


JLS의 Constructor Modifiers 섹션 은 다음과 같이 명확하게 말합니다.

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.

따라서 생성자를 동기화 할 필요가 없습니다.

Also it is not recommended to give out the objects reference(this) before object is created. One of the possible ambiguous situations would be to give out the objects reference is superclass constructor when subsclass object is being created.


I see little reason to forbid constructors to be synchronized. It would be useful in many scenarios in multi-threaded applications. If I understand the Java Memory Model correctly (I read http://jeremymanson.blogspot.se/2008/11/what-volatile-means-in-java.html) the following simple class could have benefited from a synchronized constructor.

public class A {
    private int myInt;

    public /*synchronized*/ A() {
        myInt = 3;
    }

    public synchronized void print() {
        System.out.println(myInt);
    }
}

In theory, I believe a call to print() could print "0". This could happen if an instance of A is created by Thread 1, the reference to the instance is shared with Thread 2, and Thread 2 calls print(). If there is no special synchronization between the write myInt = 3 of Thread 1 and the read of the same field by Thread 2, Thread 2 is not guaranteed to see the write.

A synchronized constructor would fix this issue. Am I right about this?


The following code can achieve the expected result for synchronized constructor.

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

Such a synchronization might make sense in some very rare cases, but I guess, it's just not worth it:

  • you can always use a synchronized block instead
  • it'd support coding in a pretty strange way
  • on what should it synchronize? A constructor is a sort-of static method, it works on an object but gets called without it. So synchronizing on the class also makes (some) sense!

When in doubt, leave it out.


Note that constructors cannot be synchronized — using the synchronizedkeyword with a constructor is a syntax error. Synchronizing constructors doesn't make sense, because only the thread that creates an object should have access to it while it is being constructed.

참고URL : https://stackoverflow.com/questions/4880168/why-cant-java-constructors-be-synchronized

반응형