8

これは、負荷テスト中にTomcatNIOコネクタをテストするときに思い浮かびました。私はThreadLocalを利用し、さらにSpringを使用しています。これは、いくつかの場所でそれも利用していることを知っています。

NIOコネクタには接続ごとのスレッドがないため、クリーンアップされる前にThreadLocalオブジェクトが別のスレッドと共有された場合、バグを見つけるのが非常に困難になる可能性があるのではないかと心配しています。ただし、これは私が見つけた文書化された警告ではなく、これについて警告している他の投稿も見つからなかったため、これは問題ではないと思います。NIOコネクタは、実際の要求を処理するスレッドには影響しないと思います。

この仮定に進む前に、私はいくつかの具体的な証拠を見つけることを望んでいました。

4

3 に答える 3

8

Tomcatコードに精通している人だけが具体的な答えを出すことができますが、私は木製のものを試してみます:)

まず、単にNIOコネクタを使用するのか、非同期サーブレットについても話しているのかを明確にする必要があります。答えはそれぞれの場合でわずかに異なります。

注意すべき主なことは、Javaには、継続、コルーチン、またはスレッドの再スケジュールがないということです。つまり、スレッドで実行されているコードを起動すると、完了するまでそのコードのみ がスレッドで実行されます。

したがって、次の場合:myObject.doSomething();しばらくのdoSomething間、そのスレッドへの排他的アクセス権があります。使用しているIOモデルの種類に関係なく、スレッドは他のコードに切り替わることはありません。

発生する可能性がある(発生する)のは、異なるスレッドが異なるCPUで実行されるようにスケジュールされているが、各スレッドは1つのコードを実行して完了することです。

したがって、doSomething次の場合:

public static final ThreadLocal<MyClass> VALUE = new ThreadLocal<MyClass>();
public void doSomething() {
  VALUE.set(this);
  try {
    doSomethingElse();
  } finally {
    VALUE.set(null);
  }
}

そうすれば、心配する必要はありませんdoSomethingElse。単一のスレッドを1つ実行し、threadlocalが実行全体に対して適切な値に設定されます。

したがって、単純なNIOコネクタは違いを生まないはずです。コンテナはサーブレットのserviceメソッドを呼び出し、サーブレットは単一のスレッドで実行され、最後にすべて完了します。コンテナが接続を処理するときに、より効率的な方法でIOを処理できるというだけです。

非同期サーブレットを使用している場合は少し異なります。その場合、サーブレットは1つのリクエストに対して複数回呼び出される可能性があり(非同期モデルの動作方法のため)、それらの呼び出しは異なるスレッドで行われる可能性があるため、次のことができます。サーブレットの呼び出しの間にスレッドローカルに何かを格納しないでください。ただし、サービスメソッドを1回呼び出すだけでも、問題はありません。

HTH。

于 2011-10-28T06:03:44.793 に答える
3

確認するために、 Tomcatメーリングリストからここで確認できるように、それはまだリクエストを処理する1つのスレッドです

于 2013-07-30T13:50:39.837 に答える
2

Timからの受け入れられた回答と、pacmanからのフォローアップの質問に追加するには、AsyncResponseまたは同様の機能をNIOコネクタと一緒に使用する場合は注意が必要です。ティムが何を意味するのかわかりません。「[非同期]サーブレットが1つのリクエストに対して複数回呼び出される可能性があります」...しかし、「リクエスト」が単一の「GET」、「PUT」、「POST」を指す場合、または「DELETE」してからAFAIKを実行すると、サーブレット内の対応するリソースメソッドが1回呼び出されます。

ThreadLocalsと非同期リソースで発生する可能性のある問題の1つは、非同期リソースの処理スレッドがNIOイベントループスレッドからのThreadLocal変数のコピーを必要とするかどうかです。つまり、NIOイベントループスレッドはリクエストを受け入れてから、非同期リソースに制御を渡します...次に、そのリソースは子スレッドに制御を渡します...次に、NIOイベントループスレッドは別のリクエストを自由に処理できます... NIOイベントループスレッド内のThreadLocal変数は、後続のリクエストによって踏みにじられる可能性があります。

新しいリクエストごとにThreadLocalに格納されているオブジェクトの新しいインスタンスを作成することも可能であることに注意してください...その場合、新しいリクエストはそれぞれ、以前のリクエスト中に同じThreadLocalに格納されていた古いインスタンスを踏みにじることはありません。 。しかし、どのケースを扱っているかを確認する必要があります...いくつかの例を見てみましょう。

元の質問はSpringに言及しているので、良い例はThreadLocalを持つRequestContextHolderです。NIOイベントループスレッドの名前が「http-nio-8080-exec-1」で、AsyncResponseリソースに制御を渡し、AsyncResponseリソースがExecutorを介して新しいスレッド(「pool-2-thread-3」)を起動するとします。 。新しいスレッドには、AsyncResponse.resume()を介して返される回答を取得するためにRequestAttributesから何かを必要とするコードが含まれています。スレッド「pool-2-thread-3」で実行されているコードは「http-nio-8080-exec-1」からRequestAttributesにアクセスする必要があるため、次の2つのことを確認する必要があります。

1)リソースは、「http-nio-8080-exec-1」からRequestAttributesへの参照を取得し、それを「pool-2-thread-3」に渡します。

2)「http-nio-8080-exec-1」が新しいリクエストを受け入れると、RequestAttributesの新しいコピーが作成され、新しいリクエストのRequestContextHolderのThreadLocalコピーに設定されます(Springコードはこのように機能するため、注意してください)。安全です)。

反対の例は、マップのlog4jMDCThreadLocalコピーです。この場合、新しいリクエストはそれぞれ同じマップを再利用します...したがって、マップの参照をNIOイベントループスレッドからAsyncResponseスレッドに渡すことは安全ではありません...マップのコピーを作成して渡す必要があります。これを行う方法の例については、MDCAwareThreadPoolExectutorを参照してください。

基本的に、NIOイベントループスレッドからAsyncResponseスレッドに渡す必要がある各ThreadLocal変数をチェックする必要があります...そして、元のオブジェクトへの参照を渡すだけで安全かどうか、または必要があるかどうかを確認します。ワーカースレッドのThreadLocal変数にコピーを設定する前に、オブジェクトのコピーを作成します。

ところで、上記の2つの例を組み合わせたコードを次に示します。

public class RequestContextAwareThreadPoolExecutor extends MDCAwareThreadPoolExecutor {
    /* ... constructors left out ... */

    @Override
    public void execute(Runnable runnable) {
        super.execute(wrap(runnable, RequestContextHolder.currentRequestAttributes()));
    }

    Runnable wrap(final Runnable runnable, final RequestAttributes requestAttributes) {
        return () -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            try {
                runnable.run();
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }
}

AsyncResponseリソースから、次のように呼び出すだけです。

executor.execute(() -> {
    // veryLongOperation() needs to access the RequestAttributes and the MDC
    asyncResponse.resume(veryLongOperation());
});
于 2016-10-13T18:52:42.687 に答える