Tomcatはスレッドプールを使用してリモート同時リクエストを処理します。

Tomcatはスレッドプールを使用してリモート同時リクエストを処理します。

Tomcatが同時リクエストを処理する方法を理解することで、スレッドプール、ロック、キュー、および安全でないクラスについて理解することができます。次のメインコードは

java-jre:

sun.misc.安全でない
java.util.concurrent.ThreadPoolExecutor
java.util.concurrent.ThreadPoolExecutor.ワーカー
java.util.concurrent.locks.AbstractQueuedSynchronizer
java.util.concurrent.locks.AbstractQueuedLongSynchronizer
java.util.concurrent.LinkedBlockingQueue

トムキャット:

org.apache.tomcat.util.net.Nioエンドポイント
org.apache.tomcat.util.threads.ThreadPoolExecutor
org.apache.tomcat.util.threads.タスクスレッドファクトリー
org.apache.tomcat.util.threads.タスクキュー

スレッドプールエグゼキュータ

スレッドを管理し、スレッドのオーバーヘッドを削減するスレッドプール実装クラスです。タスクの実行効率を向上させるために使用できます。

コンストラクタのパラメータは

パブリックスレッドプールエグゼキュータ(
 int コアプールサイズ、
 int 最大プールサイズ、
 長いkeepAliveTime、
 TimeUnit 単位、
 BlockingQueue<Runnable> ワークキュー、
 スレッドファクトリー スレッドファクトリー、
 RejectedExecutionHandler ハンドラー) {
 
}

corePoolSizeはコアスレッドの数です
maximumPoolSizeはスレッドの最大数です
keepAliveTime 非コアスレッドの最大アイドル時間(時間を超えた場合は終了)
単位時間単位
workQueueキュー、タスクが多すぎる場合は、まずキューに格納します
threadFactory スレッドファクトリー、スレッドを作成するファクトリー
ハンドラーの決定戦略。タスクが多すぎてキューにタスクを格納できなくなった場合にどうするか、このオブジェクトがそれを処理します。これはインターフェースであり、処理方法をカスタマイズできます

Tomcat の http リクエストにおける ThreadPoolExecutor の適用

このスレッドプールは、Tomcatがリモートリクエストを受け取った後、各リクエストを個別のタスクとして処理するために使用されます。execute(Runnable)が呼び出されるたびに

初期化

org.apache.tomcat.util.net.NioEndpoint

NioEndpointが初期化されると、スレッドプールが作成される。

パブリック void createExecutor() {
 内部エグゼキュータ = true;
 タスクキュー taskqueue = 新しいタスクキュー();
 //TaskQueue は無制限のキューであり、いつでも追加できるため、ハンドラーは無効な TaskThreadFactory と同等です。tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
 executor = 新しい ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
 taskqueue.setParent( (ThreadPoolExecutor) executor);
 }

スレッドプールが作成されたら、prestartAllCoreThreads()を呼び出してコアワーカースレッドを初期化し、

パブリック int prestartAllCoreThreads() {
 整数n = 0;
 (addWorker(null, true)) の間
  ++n;
 n を返します。
 }

addWorker の数が corePoolSize と等しい場合、addWorker(null,true) は false を返し、ワーカー スレッドの作成を停止します。

タスクをキューに送信する

クライアントがリクエスト (http) するたびに、処理タスクが送信されます。

ワーカーはキューからタスクを取得して実行します。以下は、タスクをキューに入れるロジック コードです。

ThreadPoolExecutor.execute(Runnable) はタスクを送信します。

パブリック void execute(実行可能なコマンド) {
 if (コマンド == null)
  新しい NullPointerException() をスローします。
 
 ctl.get(); を呼び出します。
 	// ワーカーの数はコア スレッドの数より少ないですか? Tomcat での初期化後、通常は最初の条件は満たされず、addWorker は呼び出されません。
 ワーカー数(c) < コアプールサイズの場合
  if (addWorker(コマンド、true))
  戻る;
  ctl.get() 関数は、次のコードで使用できます。
 }
 	// workQueue.offer(command) タスクをキューに追加します。
 if (isRunning(c) && workQueue.offer(command)) {
  int 再チェック = ctl.get();
  if (!isRunning(再確認) && remove(コマンド))
  拒否(コマンド);
  そうでない場合 (workerCountOf(recheck) == 0)
  ワーカーを追加します(null、false);
 }
 そうでない場合 (!addWorker(command, false))
  拒否(コマンド);
 }

workQueue.offer(command) はタスクの送信を完了します (Tomcat がリモート http 要求を処理するとき)。

ワークキューオファー

TaskQueue は BlockingQueue の具体的な実装クラスであり、workQueue.offer(command) の実際のコードは次のとおりです。

パブリックブールオファー(E e) {
 e == null の場合、新しい NullPointerException() をスローします。
 最終的な AtomicInteger count = this.count;
 count.get() == 容量の場合
 false を返します。
 c = -1;
 ノード<E> ノード = 新しいノード<E>(e);
 最終的な ReentrantLock putLock = this.putLock;
 ロックを置きます。
 試す {
 count.get() が容量未満の場合
  enqueue(node); //ここでキューにタスクを追加します c = count.getAndIncrement();
  (c + 1 < 容量)の場合
  シグナルが満杯でない。
 }
 ついに
 ロックを解除します。
 }
 (c == 0)の場合
 シグナルが空でない();
 c >= 0 を返します。
}

// タスクをキューに追加します/**
 * キューの最後にノードをリンクします。
 *
 * @param node ノード
 */
プライベートvoidエンキュー(Node<E>ノード) {
 // putLock.isHeldByCurrentThread() をアサートします。
 // last.next == null であることを確認します。
 last = last.next = node; //リンクリスト構造last.next = node; last = node
}

後はワーカーの仕事です。run メソッドではワーカーが getTask() を呼び出してここで送信されたタスクを取得し、実行します。

スレッド プールは新しく送信されたタスクをどのように処理しますか?

ワーカーを追加したら、タスクを送信します。ワーカーの数が corePoolSize に達すると、タスクはキューに入れられ、ワーカーの run メソッドがループしてキュー内のタスクを取得します (キューが空でない場合)。

ワーカー実行方法:

/** メイン実行ループを外側の runWorker に委譲します */
 パブリックボイド実行() {
  実行ワーカー(これを);
 }

キュー内のタスクをループする

RunWorker(worker) メソッドのループ コード:

最終的なvoid runWorker(ワーカーw) {
 スレッド wt = Thread.currentThread();
 実行可能なタスク = w.firstTask;
 w.firstTask = null;
 w.unlock(); // 割り込みを許可する
 ブール値completedAbruptly = true;
 試す {
  while (task != null || (task = getTask()) != null) { // キュー内のタスクを取得するためにループします w.lock(); // ロックします try {
   //実行前処理 beforeExecute(wt, task);
   //キュー内のタスクはtask.run()の実行を開始します。
   // 後処理を実行します afterExecute(task, throw);
  ついに
   タスク = null;
   完了したタスク++;
   w.unlock(); // ロックを解除する}
  }
  突然完了 = false;
 ついに
  processWorkerExit(w, 突然完了);
 }
 }

task.run()はタスクを実行する

ロックアプリケーション

ThreadPoolExecutor はロックを使用して次の 2 つのことを保証します。
1. 他のスレッドがキューを操作できないように、キューにタスクを追加します。
2. 他のスレッドが同時にキューを操作できないようにキューのタスクを取得します。

キューにタスクロックを追加する

パブリックブールオファー(E e) {
 e == null の場合、新しい NullPointerException() をスローします。
 最終的な AtomicInteger count = this.count;
 count.get() == 容量の場合
  false を返します。
 c = -1;
 ノード<E> ノード = 新しいノード<E>(e);
 最終的な ReentrantLock putLock = this.putLock;
 putLock.lock(); //ロックしてみる{
  count.get() が容量未満の場合
  エンキュー(ノード);
  カウントの取得と増分();
  (c + 1 < 容量)の場合
   シグナルが満杯でない。
  }
 ついに
  putLock.unlock(); //ロックを解除する}
 (c == 0)の場合
  シグナルが空でない();
 c >= 0 を返します。
 }

キュータスクロックを取得

プライベート実行可能getTask() {
 boolean timedOut = false; // 最後の poll() はタイムアウトしましたか?
		// ...省略 for (;;) {
  試す {
  実行可能 r = 時間制限あり ?
   workQueue.poll(keepAliveTime、時間単位.NANOSECONDS):
   workQueue.take(); //キュー内のタスクを取得する if (r != null)
   r を返します。
  タイムアウト = true;
  } catch (InterruptedException 再試行) {
  タイムアウト = false;
  }
 }
 }
パブリックE take()はInterruptedExceptionをスローします{
 元;
 c = -1;
 最終的な AtomicInteger count = this.count;
 最終的な ReentrantLock takeLock = this.takeLock;
 takeLock.lockInterruptibly(); // ロックを試みる {
  count.get() == 0 の間
  notEmpty.await(); //キューにタスクがない場合は待機します}
  x = デキュー();
  c = count.getAndDecrement();
  (c > 1)の場合
  空ではないシグナル();
 ついに
  takeLock.unlock(); // ロックを解除する}
 (c == 容量)の場合
  シグナルが満杯でない();
 x を返します。
 }

不安定な

並行シナリオでは、このキーワードはメンバー変数を変更するために非常によく使用されます。

主な目的は、パブリック変数が1つのスレッドによって変更されると、他のスレッドから見えるようにすることです(リアルタイム)

sun.misc.安全でない高並行性関連クラス

スレッド プールを使用する場合、Unsafe クラスがよく使用されます。このクラスは、いくつかのアトミック CAS 操作、スレッドのロック、スレッドの解放などを高い並行性で実行できます。

sun.misc.Unsafeクラスは低レベルのクラスです。

アトミックデータ操作

java.util.concurrent.locks.AbstractQueuedSynchronizerクラスには、アトミック操作を保証するコードがあります。

保護された最終的なブール値の compareAndSetState(int expect, int update) {
 // これをサポートするための組み込み関数の設定については以下を参照してください
 unsafe.compareAndSwapInt(this、stateOffset、expect、update) を返します。
 }

Unsafe クラスに対応するコード:

//対応するJavaの最下層は実際にはネイティブメソッドであり、C++コードに対応しています/**
* Java変数が現在<tt>x</tt>である場合、アトミックに更新します。
* <tt>期待どおり</tt>を保持します。
* 成功した場合は @return <tt>true</tt>
*/
パブリックファイナルネイティブブールcompareAndSwapInt(Object o, long offset,
      int が期待される、
      整数x);

メソッドの機能は、アトミック操作を保証するために値を更新するだけです。オブジェクトoのメンバー変数offsetを操作する場合は、 o.offset を変更します。
高同時実行性における正確性を確保するには、o.offset を操作するときに正しい値を読み取る必要があり、高同時実行性環境でのデータ操作の有効性を確保するために、途中で他のスレッドによって値が変更されないようにする必要があります。

つまり、期待値がメモリ内の値と同じ場合、つまり期待値 == メモリ内の値の場合、更新された値は x となり、変更が成功したことを示す true が返されます。

それ以外の場合、期待値はメモリ値と異なり、値が他のスレッドによって変更されており、x に更新できないことを示します。アトミック変更が失敗したことをオペレーターに通知するために、False が返されます。

スレッドのブロックと起動

public native void park(boolean isAbsolute, long time); //現在のスレッドをブロックする

スレッド プールのワーカー ロールは、キュー タスクを取得するためにループします。キューにタスクがない場合、worker.run は待機中のままで、スレッドは終了しません。コードでは、 notEmpty.await()を使用してこのワーカー スレッドを中断し、待機中のスレッド キュー (タスク キューとは異なる) に入れます。新しいタスクが必要な場合は、 notEmpty.signal()を使用してこのスレッドを起動します。

最下層は
unsafe.park() は現在のスレッドをブロックします
パブリックネイティブ void park(boolean isAbsolute, long time);

unsafe.unpark() はスレッドを起動します
パブリックネイティブ void unpark(オブジェクトスレッド);

この操作は対応しています。ブロックされている場合は、まずスレッドをキューに入れます。起動されている場合は、ブロックされているスレッドをキューから取り出し、unsafe.unpark(thread) で指定されたスレッドを起動します。

java.util.concurrent.locks.AbstractQueuedLongSynchronizer.ConditionObjectクラス

リンクリストを通じてスレッド情報を保存する

// ブロッキングスレッドを追加する private Node addConditionWaiter() {
  ノード t = lastWaiter;
  // lastWaiter がキャンセルされた場合は、クリーンアップします。
  if (t != null && t.waitStatus != Node.CONDITION) {
  リンク解除CancelledWaiters();
  t = 最後のウェイター;
  }
  ノード node = new Node(Thread.currentThread(), Node.CONDITION);
  t == nullの場合
  firstWaiter = ノード;
  それ以外
  t.nextWaiter = ノード;
  lastWaiter = node; // 新しくブロックされたスレッドをリンク リストの最後に配置します。 return node;
 }

// ブロックされたスレッドを取り出す public final void signal() {
  if (!isHeldExclusively())
  新しい IllegalMonitorStateException() をスローします。
  Node first = firstWaiter; //リンクリスト内の最初のブロックされたスレッド if (first != null)
  doSignal(最初に);
 }

//取得後、このスレッドを起動します final boolean transferForSignal(Node node) {
  LockSupport.unpark(node.thread);
 true を返します。
 }
パブリック静的void unpark(スレッドスレッド) {
 (スレッド!= null)の場合
  UNSAFE.unpark(スレッド);
 }

Tomcat がスレッド プールを使用してリモート同時要求を処理する方法について説明したこの記事はこれで終わりです。Tomcat スレッド プールによるリモート同時要求の処理の詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • コネクタコンポーネントから Tomcat のスレッドモデルを見る - BIO モード (推奨)
  • Webリクエストと処理のTomcatソースコード分析
  • リクエストを処理するためのTomcatのスレッドモデルの詳細な説明

<<:  mysql と oracle のデフォルトのトランザクション分離レベルの説明

>>:  HTML テーブル マークアップ チュートリアル (37): 背景画像属性 BACKGROUND

推薦する

アルバムと写真をアルバムに保存するためのWeChatアプレット

私は現在、Xiao Nian Gao に似たビデオおよびツール アプリを開発しています。ユーザーが作...

JS の効率的なマジック演算子の概要

JavaScript は現在、毎年新しいバージョンがリリースされており、より便利で効率的な新しい演算...

mysql 8.0.16 winx64 および Linux でルート ユーザーのパスワードを変更する方法

データベースへの接続などの基本的な操作はご自身で行ってください。この記事ではパスワードの変更方法を中...

CSSはコーナーカット+ボーダー+投影+コンテンツ背景色のグラデーション効果を実現します

CSS を使用するだけで、コーナーカット + ボーダー + 投影 + コンテンツの背景色のグラデーシ...

Bash スクリプトを使用して Linux のメモリ使用量を監視する方法

序文Linux システムのパフォーマンスを監視するために使用できるオープンソースの監視ツールが市場に...

Reactにおける制御されたコンポーネントと制御されていないコンポーネントの簡単な分析

目次制御されていないコンポーネント制御コンポーネント知らせ結論は制御されていないコンポーネントフォー...

docker を使用して Django テクノロジー スタック プロジェクトをデプロイする方法

Docker の人気と成熟に伴い、Docker は徐々にプロジェクトをデプロイするための第一の選択肢...

Vue+SpringBoot+Shiroのクロスドメイン問題を解決する

目次1. Vueフロントエンドを構成する1. クロスドメイン構成を開発する2. 本番環境のクロスドメ...

Docker Compose ワンクリック ELK デプロイ方式の実装

インストールFilebeat は、より軽量でより安全なため、Logstash-Forwarder に...

JavaScript のドキュメント オブジェクト モデル (DOM)

目次1. DOMとは何か2. 要素を選択する3. getElementById() 4. クエリセレ...

MySQL データ アーカイブ ツール mysql_archiver の詳細な説明

目次I. 概要2. pt-archiverの主なパラメータ3. mysql_archiverのインス...

有名なウェブサイトのロゴに使われている25種類のフォントのコレクション

この記事では、25 の有名な Web サイト (Google、Yahoo、Twitter、Digg ...

理論: 2年間のユーザーエクスペリエンス

<br />国内のウェブサイトが本格的に普及し、ユーザーエクスペリエンスに重点が置かれる...

ドロップダウンリスト選択ボックスを実装するJavaScript

この記事の例では、ドロップダウンリスト選択ボックスを実装するためのJavaScriptの具体的なコー...

CentOS7 に ElasticSearch 6.4.1 をインストールするための詳細なチュートリアル

1. ElasticSearch 6.4.1 インストール パッケージを次の場所からダウンロードしま...