3

いくつかの「観察可能な」プロパティを持つクラスを定義しました。内部的に、クラスには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次に、メソッドを呼び出してFoo2番目のロックを取得しようとします。ただし、同時に、I / OスレッドはsetState(State)ロックオンの取得を呼び出しますFoo。これにより、プロパティ変更イベントが含まれているクラスに伝播され、Barこのクラスのロックを取得しようとします...デッドロックが発生します。つまり、プロパティ変更コールバックの設計は、ロックが取得される順序を効果的に制御できないことを意味します。

私の現在の回避策は、状態を作成してキーワードvolatileを削除することsynchronizedですが、これは厄介なようです。一つには、それはプロパティ変更イベントが発生する順序が保証されていないことを意味します。

4

1 に答える 1

3

他のスレッドが通知ループからクラスにコールバックする必要がある場合は、メッセージ全体を同期するのではなく、同期ブロックを使用して同期の範囲を縮小する必要があります (これは投稿からコピーされ、コンパイルされるかどうかはわかりません) ):

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 ミリ秒後には異なる可能性があるということです。オブジェクトの正確な状態を知っていることを前提としたコードを書くことはできませんこれを行う必要があると感じた場合は、設計を再考する必要があります。

そして最後のコメント: Doug Lea はあなたや私より賢いです。 java.util.concurrentのクラスを再発明しようとしないでください。

于 2009-09-24T11:14:59.830 に答える