PropertyChangeListener を使用したロック戦略
-
16-09-2019 - |
質問
多数の「監視可能な」プロパティを持つクラスを定義しました。内部的には、クラスには I/O を実行する単一のスレッドが含まれています。例えば
public class Foo {
private final PropertyChangeSupport support;
private State state;
public Foo() { this.support = new PropertyChangeSupport(this); }
public synchronized State getState() { return state; }
public synchronized void setState(State state) {
if (this.state != state) {
State oldState = this.state;
this.state = state;
// Fire property change *whilst still holding the lock*.
support.firePropertyChange("state", oldState, state);
}
}
public synchronized void start() {
// Start I/O Thread, which will call setState(State) in some circumstances.
new Thread(new Runnable() ...
}
}
私の質問は次のとおりです。クラスのロックを保持している間はプロパティ変更イベントの発生を避けるべきでしょうか?... または おそらく、単一の専用スレッド (例:"event-multicaster" スレッド)?
現在の設計では、スレッド A が外部クラスのロックを解除することによりデッドロックが発生します。 Bar
, 、そして次にメソッドを呼び出そうとします Foo
そして2番目のロックを外します。ただし、同時に I/O スレッドは setState(State)
ロックオンを取得する Foo
, 、プロパティ変更イベントをそれを含むクラスに伝播します。 Bar
そしてこのクラスのロックを取得しようとします...デッドロックが発生します。言い換えれば、プロパティ変更コールバックの設計は、ロックが取得される順序を効果的に制御できないことを意味します。
私の現在の回避策は、状態を作成することです volatile
そして、を削除します synchronized
キーワードですが、これは面倒なようです。1 つは、プロパティ変更イベントが発生する順序が保証されていないことを意味します。
解決
他のスレッドが通知ループからクラスにコールバックする必要がある場合は、メッセージ全体を同期するのではなく、同期ブロックを使用して、同期の範囲を減らす必要があります (これは投稿からコピーしたものですが、コンパイルできるかどうかはわかりません) ):
public void setState(State state) {
State oldState = null;
synchronized (this) {
if (this.state != state) {
oldState = this.state;
this.state = state;
}
}
if (oldState != null)
support.firePropertyChange("state", oldState, state);
}
ロックを保持する時間はできるだけ短くしてください。そして、これらのコールバックを同期されたメッセージ キューに置き換えることを検討してください (java.util.concurrent を参照)。
編集して、答えを一般化し、いくつかの哲学を追加します。
まず暴言を吐く:マルチスレッドプログラミングは難しいです。あなたに違うことを言う人は、あなたに何かを売りつけようとしているのです。マルチスレッド プログラムで相互依存する接続を多数作成すると、完全に狂気ではないにしても、微妙なバグが発生します。
私が知る限り、マルチスレッド プログラミングを簡素化する最善の方法は、開始点と終了点が明確に定義された独立したモジュールを作成することです。 俳優モデル. 。個々のアクターはシングルスレッドです。単独で簡単に理解してテストできます。
純粋なアクター モデルはイベント通知によく適合します。アクターはイベントに応答して呼び出され、何らかのアクションを実行します。開始後に別のイベントが発生したかどうかは関係ありません。場合によっては、それだけでは不十分な場合があります。別のスレッドによって管理されている状態に基づいて決定を下す必要があります。それで大丈夫です:アクターはその状態を (同期した方法で) 見て、決定を下すことができます。
(Tom Hawtin がコメントで述べているように) 覚えておくべき重要なことは、現在読み取っている状態が、1 ミリ秒後には異なる可能性があるということです。あなたはできません これまで オブジェクトの正確な状態を知っていることを前提としたコードを作成します。これを行う必要があると感じる場合は、設計を再考する必要があります。
そして最後のコメント:ダグ・リーはあなたや私よりも賢いです。クラスを再発明しようとしないでください java.util.concurrent.