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()
でcontractを確立することを除いて、2つのインターフェイスが同じように動作すると主張できます。 Interface1
は何をしますか。もちろん、default
実装は、具体的な実装状態について何の仮定もしてはならない、またはそのようなキーワードは単にその重みを引き出さないと主張するかもしれません。
JSR-335エキスパートグループがインターフェイスメソッドでsynchronized
をサポートしないことにした理由は何ですか?
最初はデフォルトのメソッドでsynchronized
修飾子をサポートしたいのは明らかなように思えるかもしれませんが、そうすることは危険であり、禁止されていることがわかりました。
同期メソッドは、ロックオブジェクトがレシーバーであるsynchronized
ブロックでボディ全体が囲まれているように動作するメソッドの省略形です。このセマンティクスをデフォルトのメソッドにも拡張することは賢明に思えるかもしれません。結局のところ、これらもレシーバーを持つインスタンスメソッドです。 (synchronized
メソッドは完全に構文最適化であることに注意してください。これらは必要ではなく、対応するsynchronized
ブロックよりもコンパクトです。これは時期尚早の構文であるという合理的な議論がありますそもそも最適化、そして同期化されたメソッドは解決するよりも多くの問題を引き起こしますが、その船はずっと前に航海しました。
それで、なぜ彼らは危険なのですか?同期はロックに関するものです。ロックとは、可変状態への共有アクセスを調整することです。各オブジェクトには、どのロックがどの状態変数を保護するかを決定する同期ポリシーが必要です。 ( Javaの同時実行性 、セクション2.4を参照してください。)
多くのオブジェクトは、同期ポリシーとしてJava Monitor Pattern(JCiP 4.1)を使用します。この場合、オブジェクトの状態は固有のロックによって保護されます。このパターンには魔法や特別なものはありませんが、便利ですが、メソッドでのsynchronized
キーワードの使用は暗黙的にこのパターンを想定しています。
そのオブジェクトの同期ポリシーを決定するのは、状態を所有するクラスです。しかし、インターフェースは、それらが混合されるオブジェクトの状態を所有していません。したがって、インターフェースで同期メソッドを使用すると、特定の同期ポリシーが想定されますが、仮定する合理的な根拠がないため、同期を使用しても、追加のスレッドセーフはまったく提供されません(間違ったロックで同期している可能性があります)。これにより、スレッドセーフについて何かをしたという誤った自信が得られ、間違った同期ポリシーを想定しているというエラーメッセージは表示されません。
単一のソースファイルの同期ポリシーを一貫して維持することは、すでに十分に困難です。サブクラスがスーパークラスによって定義された同期ポリシーに正しく準拠することを保証することはさらに困難です。そのような疎結合クラス(インターフェースと、それを実装する可能性のある多くのクラス)の間でそうしようとすることは、ほぼ不可能であり、エラーが発生しやすくなります。
反対するすべての議論を考えると、何のための議論でしょうか?彼らは主にインターフェイスを特性のように振る舞わせることについてのようです。これは理解できる願望ですが、デフォルトのメソッドのデザインセンターは、「Traits--」ではなくインターフェイスの進化です。 2つが一貫して達成できる場合は、そうするよう努めましたが、一方が他方と競合する場合は、主要な設計目標を優先して選択する必要がありました。
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オブジェクトには異なるオブジェクトロックがあることがわかります。すべてのロックは独立しています。したがって、この場合、親クラスまたは共通インターフェースで同期を使用しても危険ではないと思います。誰もこれについてもっと説明できますか?