0

メインスレッドによってインスタンス化されているクラスがあります。次に、このクラスは2番目のスレッドである処理スレッドを生成します。処理スレッドは、フィールドにアクセス/変更するクラスの特定のメソッド(処理メソッド)を呼び出します。これらのメソッドとフィールドは、処理スレッド以外からアクセスされることはありません。ただし、それらを初期化するコンストラクターはメインスレッドで実行されます。

このクラスは、受信したメッセージを処理するための関数を呼び出す入力処理スレッドを含む汎用の「プロトコル」クラスを拡張します。もともと、私はジェネリッククラスのコンストラクターで処理スレッドを自動的に開始していましたが、これはひどくばかげた考えであることが判明しました。

  1. スーパーコンストラクターと呼ばれるサブクラス
  2. スーパーコンストラクターがスレッドを開始しました
  3. スレッドは、空のメッセージを使用してメッセージ処理メソッドをすぐに呼び出しました(プロトコルの最初のメッセージを送信するため)。このメソッドは「送信メッセージカウンター」を設定します。
  4. メインスレッドで、スーパーコンストラクターが返され、サブクラスが設定されたメッセージカウンターを初期化して、ゼロにリセットしました。

処理スレッドの開始を別のメソッドに移動し、サブクラスのコンストラクターの最後で呼び出すことで、これを変更しました。

public ProtocolSubclass() {
    super();
    startProcessingThread();
}

startProcessingThreads()を呼び出すと、フィールドが初期化されることが保証されていると思います。startProcessingThread()が呼び出された後、フィールドにはそのスレッドからのみアクセスされます。これはできますか?フィールドはメインスレッドで初期化されますが、処理スレッドで読み取られるため、フィールドを揮発性としてマークする必要がありますか?

今回は正解だったと思いますが、上記の問題を何時間もデバッグした後、むしろ質問したいと思います...

要求に応じて、ここにもう少し詳細な(まだ簡略化された)コードがあります。上記のコードははるかに単純化されているため、以下のコードと正確に一致しない場合があることに注意してください。動作していたフィールドはcurrentMsgでした:

public abstract class ProtocolConnection {
    public ProtocolConnection(/*stuff*/) {
        /*stuff*/
        // DO NOT DO THIS HERE: startProcessingThreads();
    }

    protected void startProcessingThreads() {
        inputProcessingThread.start();
    }

    private final Thread inputProcessingThread = new Thread() {
        public void run() {
            if (isInitiator) initiateConnection();
            while (!closed && !finished) {
                ProtocolMessage msg = new ProtocolMessage(inputStream);
                log("received", Integer.toString(msg.tag), Integer.toString(msg.length));
                ProtocolConnection.this.processMessage(msg);
            }
        }
    };
}


public class SimpleProtocolConnection extends ProtocolConnection {
    private int currentMsg = 0;

    public SimpleProtocolConnection(/*stuff*/) {
        super(/*stuff*/);
        startProcessingThreads();
    }

    @Override
    protected void processMessage(ProtocolMessage msg) {
        if (msg.tag != LAST_MESSAGE) {
            sendNext();
        }
    }

    @Override
    protected void initiateConnection() {
        sendNext();
    }

    private void sendNext() {
        addToSendingQueue(new ProtocolMessage(currentMsg, getData())); // very simplified
        currentMsg++;
    }

}
4

2 に答える 2

2

フィールドはスレッド 1 で初期化されます。次に、スレッド 2 が開始されます。次に、スレッド 2 がフィールドに排他的にアクセスします。正しい?もしそうなら...

揮発性/原子は必要ありません。

JLSに基づいて、スレッド B が開始される前にスレッド A で実行されたアクションは、スレッド B に表示されます。これは、いくつかの異なる方法で記述されます。

17.4.2. 行動

スレッド間アクションは、あるスレッドによって実行されるアクションであり、別のスレッドによって検出または直接影響を受ける可能性があります。プログラムが実行できるスレッド間アクションには、いくつかの種類があります。

[...]

スレッドを開始するか、スレッドが終了したことを検出するアクション (§17.4.4)。

--

17.4.4. 同期順序

すべての実行には同期順序があります。同期順序は、実行のすべての同期アクションに対する合計順序です。各スレッド t について、t の同期アクション (§17.4.2) の同期順序は、t のプログラム順序 (§17.4.3) と一致しています。

同期アクションは、次のように定義されたアクションの同期済み関係を誘導します。

[...]

スレッドを開始するアクションは、それが開始するスレッド内の最初のアクションと同期します。

--

17.4.5. 注文前に発生

2 つのアクションは、先行発生関係によって順序付けできます。あるアクションが別のアクションの前に発生する場合、最初のアクションは 2 番目のアクションよりも前に表示され、順序付けされます。

[...]

スレッドでの start() の呼び出しは、開始されたスレッドのアクションの前に発生します。

于 2013-03-13T03:15:05.147 に答える
-1

volatile特定のフィールドが異なるスレッドによって変更されることを意味します。コンストラクターがマークされている場合は必要ありませんsynchronized。それ以外の場合は必要です。

于 2013-03-13T02:27:52.333 に答える