Java 8 인터페이스 메소드에서 "동기화"가 허용되지 않는 이유는 무엇입니까?


210

Java 8에서는 다음과 같이 쉽게 작성할 수 있습니다.

interface Interface1 {
    default void method1() {
        synchronized (this) {
            // Something
        }
    }

    static void method2() {
        synchronized (Interface1.class) {
            // Something
        }
    }
}

클래스에서도 사용할 수있는 전체 동기화 의미론을 얻습니다. 그러나 synchronized메소드 선언에 수정자를 사용할 수는 없습니다 .

interface Interface2 {
    default synchronized void method1() {
        //  ^^^^^^^^^^^^ Modifier 'synchronized' not allowed here
    }

    static synchronized void method2() {
        // ^^^^^^^^^^^^ Modifier 'synchronized' not allowed here
    }
}

이제 한 것을 제외하고 같은 방식의 행동이 인터페이스를 주장 할 수 Interface2설정 계약method1()와에 대한 method2()것보다 조금 더 강한, Interface1수행합니다. 물론 우리는 default구현이 구체적인 구현 상태에 대해 어떤 가정도해서는 안되거나 그러한 키워드가 단순히 무게를 풀지 않을 것이라고 주장 할 수 있습니다 .

질문:

JSR-335 전문가 그룹이 synchronized인터페이스 방법 을 지원하지 않기로 결정한 이유는 무엇입니까 ?


1
Synchronized는 구현 동작이며 컴파일러가 작성한 최종 바이트 코드 결과를 변경하여 코드 옆에 사용할 수 있습니다. 메소드 선언에는 의미가 없습니다. 동기화가 추상화 계층에있는 경우 컴파일러에서 생성하는 내용을 혼동해야합니다.
Martin Strejc

@MartinStrejc : 그건 수도 생략에 대한 설명이 될 default synchronized아직 필요하지를 들어, static synchronized나는 후자의 힘이 일관성을 이유로 생략 한 것을 받아 들일 있지만.
Lukas Eder

1
synchronized하위 클래스에서 수정자가 재정의 될 수 있으므로이 질문에 값이 추가되는지 확실하지 않으므로 최종 기본 메소드와 같은 것이 있으면 중요합니다. (다른 질문)
Skiwi

@skiwi : 우선하는 인수로는 충분하지 않습니다. 서브 클래스는 synchronized수퍼 클래스에서 선언 된 메소드를 대체 하여 효과적으로 동기화를 제거 할 수 있습니다. 그러나 다중 상속 (예 : 상속 등)으로 인해 synchronized지원 하지 않고 지원 하지 않는 final것이 관련 되어 있다는 사실에 놀라지 않을 것 입니다. 그러나 그것은 추측입니다. 권위있는 이유가 있다면 궁금합니다. void x() synchronized void x()
Lukas Eder

2
>> "서브 클래스는 수퍼 클래스에서 동기화 된 것으로 선언 된 메소드를 재정 의하여 효과적으로 동기화를 제거 super할 수 있습니다." Btw에는 이러한 메소드를 "디펜더"라고하는 이유가 있습니다. 새로운 메소드를 쉽게 추가 할 수 있습니다.
bestsss

답변:


260

처음에는 synchronized기본 메소드 에서 수정자를 지원하려는 것이 분명해 보이지만 그렇게하는 것은 위험하므로 금지 된 것으로 나타났습니다.

동기화 된 메소드는 전체 본문이 synchronized잠금 오브젝트가 수신자 인 블록에 포함 된 것처럼 작동하는 메소드의 약어입니다 . 이 의미론을 기본 메소드로 확장하는 것이 합리적으로 보일 수 있습니다. 결국, 그들은 수신자와 함께 인스턴스 메소드입니다. ( synchronized메소드는 전적으로 구문 최적화입니다. 필요하지는 않으며, 해당 synchronized블록 보다 더 작습니다 .) 이는 조기에 구문 최적화였으며, 동기화 된 메소드라는 합리적인 주장이 있습니다. 그들이 해결하는 것보다 더 많은 문제를 일으키지 만, 그 배는 오래 전에 항해했습니다.)

그래서 왜 위험한가요? 동기화는 잠금에 관한 것입니다. 잠금은 공유 가능한 액세스를 변경 가능한 상태로 조정하는 것입니다. 각 객체에는 어떤 잠금이 어떤 상태 변수를 보호하는지 결정하는 동기화 정책이 있어야합니다. ( 실제 Java Concurrency , 2.4 단원을 참조하십시오 .)

많은 객체가 동기화 정책으로 Java Monitor Pattern (JCiP 4.1)을 사용하며,이 객체에서 객체의 상태는 본질적 잠금으로 보호됩니다. 이 패턴에 대한 마법이나 특별한 것은 없지만 편리하고 synchronized메서드에 키워드를 사용하면 암시 적 으로이 패턴을 가정합니다.

해당 객체의 동기화 정책을 결정하는 상태를 소유하는 클래스입니다. 그러나 인터페이스는 혼합 된 오브젝트의 상태를 소유하지 않습니다. 따라서 인터페이스에서 동기화 된 메소드를 사용하는 것은 특정 동기화 정책을 가정하지만 가정 할만한 합리적인 근거가없는 정책을 가정하므로 그럴 수도 있습니다. 동기화를 사용하면 추가 스레드 안전성이 제공되지 않습니다 (잘못된 잠금에서 동기화 중일 수 있음). 이것은 스레드 안전성에 대해 무언가를했다는 잘못된 확신을 제공하며, 잘못된 동기화 정책을 가정하고 있다는 오류 메시지가 표시되지 않습니다.

단일 소스 파일에 대한 동기화 정책을 일관되게 유지하기에는 이미 어렵습니다. 서브 클래스가 자신의 수퍼 클래스에 의해 정의 된 동기화 정책을 올바르게 준수하는지 확인하기가 더 어렵습니다. 이렇게 느슨하게 결합 된 클래스 (인터페이스와이를 구현하는 많은 클래스)간에 그렇게하는 것은 거의 불가능하고 오류가 발생하기 쉽습니다.

이러한 모든 주장에 대하여, 어떤 주장이 있을까요? 인터페이스가 특성처럼 동작하도록 만드는 데 주로 사용되는 것 같습니다. 이것은 이해할 수있는 욕구이지만 기본 방법의 디자인 센터는 "Traits--"가 아니라 인터페이스 진화입니다. 이 두 가지를 일관되게 달성 할 수있는 방법을 찾기 위해 노력했지만, 하나가 다른 것과 충돌하는 경우 기본 설계 목표에 찬성하여 선택해야했습니다.


26
또한 JDK 1.1에서 synchronized메소드 수정자는 javadoc 출력에 표시되어 사람들이 그것이 스펙의 일부라고 생각하게 만들었습니다. 이것은 JDK 1.2에서 수정되었습니다. 공개 메소드에 표시 되더라도 synchronized수정자는 계약이 아닌 구현의 일부입니다. (비슷한 추론 처리가 발생한 native개질제.)
스튜어트 마크

15
초기 Java 프로그램에서 흔히 발생하는 실수는 충분히 뿌려 synchronized지고 안전한 구성 요소를 스레드하는 것이며 거의 스레드 안전 프로그램을 사용하는 것입니다. 문제는 이것이 정상적으로 작동했지만 놀랍고 부서지기 쉬운 방식으로 고장났다는 것입니다. 잠금 작동 방식을 이해하는 것이 강력한 응용 프로그램의 핵심이라는 데 동의합니다.
피터로 레이

10
@BrianGoetz 아주 좋은 이유입니다. 그러나 왜 방법으로 synchronized(this) {...}허용 default됩니까? (Lukas의 질문에 표시된 것처럼) 기본 메소드가 구현 클래스의 상태를 소유하지 못하게합니까? 우리도 그것을 막고 싶지 않습니까? 정보가없는 개발자가 그러한 사례를 찾으려면 FindBugs 규칙이 필요합니까?
Geoffrey De Smet

17
@Geoffrey : 아니요, 이것을 제한 할 이유가 없습니다 (항상주의해서 사용해야하지만) sync 블록은 작성자가 잠금 객체를 명시 적으로 선택해야합니다. 이를 통해 해당 정책이 무엇인지 아는 경우 다른 개체의 동기화 정책에 참여할 수 있습니다. 위험한 부분은 'this'(동기화 방법이 수행하는 것)에서 동기화하는 것이 실제로 의미가 있다고 가정합니다. 보다 명확한 결정이 필요합니다. 즉, 인터페이스 메소드의 동기화 블록은 매우 드물 것으로 예상됩니다.
Brian Goetz

6
@GeoffreyDeSmet : 같은 이유로 당신은 할 수 있습니다 synchronized(vector). 안전을 원한다면 this잠금을 위해 공개 객체 (예 : 자체)를 사용해서는 안됩니다 .
Yogu

0
public class ParentSync {

public synchronized void parentStart() {
    System.out.println("I am " + this.getClass() + " . parentStarting. now:" + nowStr());
    try {
        Thread.sleep(30000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("I am " + this.getClass() + " . parentFinished. now" + nowStr());
}

private String nowStr() {
    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
}
}


public class SonSync1 extends ParentSync {
public void sonStart() {
    System.out.println("I am " + this.getClass() + ". sonStarting,calling parent now ... ");
    super.parentStart();
    System.out.println("I am " + this.getClass() + ". sonFinished");
}
}



public class SonSync2 extends ParentSync {

public void sonStart() {
    System.out.println("I am " + this.getClass() + ". sonStarting,calling parent now ... ");
    super.parentStart();
    System.out.println("I am " + this.getClass() + ". sonFinished");
}
}



public class SyncTest {
public static void main(String[] args) throws Exception {

    new Thread(() -> {
        new SonSync1().sonStart();
    }).start();

    new Thread(() -> {
        new SonSync2().sonStart();
    }).start();

    System.in.read();
}
}

결과:

I am class com.common.interface18_design.whynotsync_onmethod.SonSync1. sonStarting,calling parent now ... 
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2. sonStarting,calling parent now ... 
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2 . parentStarting. now:2019-04-18 09:50:08
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1 . parentStarting. now:2019-04-18 09:50:08
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1 . parentFinished. now2019-04-18 09:50:38
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1. sonFinished
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2 . parentFinished. now2019-04-18 09:50:38
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2. sonFinished

(부모 클래스를 예로 사용하여 죄송합니다)

그 결과 부모 클래스 잠금이 모든 하위 클래스에 의해 소유되고 SonSync1과 SonSync2 객체가 다른 객체 잠금을 가짐을 알 수있었습니다. 모든 자물쇠는 독립성입니다. 따라서이 경우 부모 클래스 또는 공통 인터페이스에서 동기화를 사용하는 것이 위험하지 않다고 생각합니다. 누구든지 이것에 대해 더 설명 할 수 있습니까?

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.