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

さて、2つのインターフェースは同じように動作すると主張できます。ただし、on とon でコントラクトInterface2確立します。これは、何よりも少し強力です。もちろん、実装は具体的な実装状態についていかなる仮定もすべきではない、またはそのようなキーワードは単にその重みを引っ張らないと主張するかもしれません。method1()method2()Interface1default

質問:

JSR-335エキスパートグループがsynchronizedインターフェースメソッドをサポートしないことにした理由は何ですか?


1
同期は実装の動作であり、コードの横で使用できるように、コンパイラーによって作成された最終バイトコードの結果を変更します。メソッド宣言では意味がありません。同期化が抽象化レイヤー上にある場合、コンパイラーが生成するものを混乱させるはずです。
Martin Strejc、2014年

@MartinStrejc:は省略の説明かもしれませんが、default synchronized必ずしもそうでstatic synchronizedはありませんが、一貫性の理由で後者が省略されている可能性があることは認めます。
Lukas Eder 2014年

1
synchronizedサブクラスで修飾子がオーバーライドされる可能性があるため、この質問が値を追加するかどうかは不明です。したがって、最終的なデフォルトメソッドとして何かがあった場合にのみ問題になります。(あなたの他の質問)
スキーウィ

@skiwi:オーバーライドする引数では不十分です。サブクラスはsynchronized、スーパークラスで宣言されているメソッドをオーバーライドし、同期を効果的に削除できます。私はサポートしていないことに驚かないだろうsynchronizedし、サポートしていないfinalかもしれないので、多重継承(例えば継承、しかし、関係しているvoid x() synchronized void x()など、)。しかし、それは推測です。信頼できる理由があれば知りたいです。
Lukas Eder

2
>>「スーパークラスで同期が宣言されたメソッドをサブクラスがオーバーライドして、同期を効果的に削除する」... super完全な再実装とプライベートメンバーへの可能なアクセスを必要とする呼び出しが行われない場合のみ。ところで、これらのメソッドが「ディフェンダー」と呼ばれるのには理由があります。これらのメソッドは、新しいメソッドを簡単に追加できるようにするために存在しています。
bestss

回答:


260

最初synchronizedはデフォルトのメソッドで修飾子をサポートしたいのは明らかなように思えるかもしれませんが、そうすることは危険であり、禁止されていました。

同期メソッドは、synchronizedロックオブジェクトがレシーバーであるブロックで本体全体が囲まれているように動作するメソッドの省略形です。このセマンティクスをデフォルトのメソッドにも拡張することは賢明に思えるかもしれません。結局のところ、これらもレシーバーを持つインスタンスメソッドです。(synchronizedメソッドは完全に構文の最適化であることに注意してください。それらは必要ではなく、対応するsynchronizedブロックよりもコンパクトです。これは、最初の段階では構文の最適化が時期尚早であり、その同期されたメソッドであるという妥当な議論があります。彼らが解決するよりも多くの問題を引き起こしますが、その船はずっと前に出航しました。

では、なぜ危険なのでしょうか。同期はロックに関するものです。ロックとは、変更可能な状態への共有アクセスを調整することです。各オブジェクトには、どのロックがどの状態変数を保護するかを決定する同期ポリシーが必要です。(実際のJava同時実行、セクション2.4を参照してください。)

多くのオブジェクトは、同期ポリシーとして、Javaモニターパターン(JCiP 4.1)を使用します。このパターンでは、オブジェクトの状態はその固有のロックによって保護されます。このパターンには魔法や特別なものはありませんが、便利であり、synchronizedメソッドでのキーワードの使用はこのパターンを暗黙的に想定しています。

オブジェクトの同期ポリシーを決定するのは、状態を所有するクラスです。ただし、インターフェイスは、それらが混在するオブジェクトの状態を所有していません。そのため、インターフェイスで同期メソッドを使用すると、特定の同期ポリシーが想定されますが、想定するための合理的な根拠がないため、同期を使用しても、スレッドの安全性はまったく向上しません(間違ったロックで同期している可能性があります)。これにより、スレッドの安全性について何かを行ったという誤った自信が得られ、誤った同期ポリシーを想定していることを示すエラーメッセージは表示されません。

単一のソースファイルの同期ポリシーを一貫して維持するのはすでに十分に困難です。サブクラスがスーパークラスによって定義された同期ポリシーに正しく準拠していることを確認することはさらに困難です。そのような疎結合クラス(インターフェースとそれを実装する可能性のある多くのクラス)の間でそうしようとすると、ほとんど不可能になり、エラーが発生しやすくなります。

それらに対するすべての議論を考えると、何のための議論になりますか?彼らは主にインターフェイスをよりトレイトのように振る舞わせるようにしているようです。これは理解できる欲求ですが、デフォルトのメソッドのデザインセンターは「特性-」ではなく、インターフェースの進化です。2つを一貫して達成できる場合は、そうするように努めましたが、一方が他方と競合する場合は、主要な設計目標を優先して選択する必要がありました。


26
また、JDK 1.1では、synchronizedメソッド修飾子がjavadoc出力に表示され、仕様の一部であると人々に誤解させることに注意してください。これはJDK 1.2で修正されました。パブリックメソッドに表示される場合でも、synchronized修飾子は実装ではなく、コントラクトの一部です。(同様の推論と処理がnative修飾子で発生しました。)
スチュアートマーク

15
初期のJavaプログラムでよくある間違いは、十分なsynchronizedスレッドセーフコンポーネントをまき散らすことであり、ほとんどスレッドセーフなプログラムがありました。問題は通常これで問題なく動作することですが、驚くほど壊れやすい方法で壊れていました。ロックがどのように機能するかを理解することが、堅牢なアプリケーションの鍵であることに同意します。
Peter Lawrey、2014

10
@BrianGoetz非常に良い理由です。しかし、なぜメソッドでsynchronized(this) {...}許可されているのdefaultですか?(Lukasの質問に示されているように)。これにより、デフォルトのメソッドが実装クラスの状態も所有できるようになりますか?私たちもそれを防ぎたくないですか?知識のない開発者がそれを行うケースを見つけるためにFindBugsルールが必要でしょうか?
Geoffrey De Smet 2014

17
@Geoffrey:いいえ、これを制限する理由はありません(常に注意して使用する必要があります)。同期ブロックでは、作成者がロックオブジェクトを明示的に選択する必要があります。これにより、そのポリシーが何であるかを知っている場合に、他のオブジェクトの同期ポリシーに参加できます。危険な部分は、「this」での同期(これは同期メソッドが行うことです)が実際に意味があると想定しています。これはより明確な決定である必要があります。とは言っても、インターフェイスメソッドの同期ブロックはかなりまれだと思います。
ブライアンゲッツ2014

6
@GeoffreyDeSmet:同じ理由で、たとえばを実行できますsynchronized(vector)。安全を確保したい場合はthis、ロックにパブリックオブジェクト(それ自体など)を使用しないでください。
Yogu、2014

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.