JDKネイティブスレッドプールのバグを修正するTomcatの実装原理

JDKネイティブスレッドプールのバグを修正するTomcatの実装原理

処理能力と同時実行性を向上させるために、Web コンテナは通常、リクエストを処理するタスクをスレッド プールに配置します。JDK のネイティブ スレッド プールは、CPU を集中的に使用するタスクに本質的に適しているため、Tomcat はこれを変更しました。

Tomcat スレッドプールの原則

実際、ThreadPoolExecutor のパラメータには主に次の重要なポイントがあります。

スレッド数を制限する

キューの長さを制限する

Tomcat はこれら 2 つのリソースを制限する必要があります。そうしないと、同時実行性が高い場合に CPU とメモリが枯渇する可能性があります。
したがって、Tomcat のスレッド プールは次のパラメータを渡します。

// カスタマイズされたタスク キュー taskqueue = new TaskQueue(maxQueueSize);

// カスタマイズされたスレッドファクトリー TaskThreadFactory tf = new TaskThreadFactory(namePrefix,
							                 デーモン、
							                 スレッド優先度を取得します。
);

// カスタム スレッド プール executor = new ThreadPoolExecutor(getMinSpareThreads(),
								  getMaxThreads()、
				 			      最大アイドル時間、 
				 			      時間単位.ミリ秒、
				 			      タスクキュー、
				 			      tf);

Tomcat にはスレッド数制限もあり、次のように設定します。

  • コアスレッド数 (minSpareThreads)
  • スレッドプールの最大数 (maxThreads)

Tomcat スレッド プールには独自の特別なタスク処理フローもあり、execute メソッドを書き換えることで独自の特別なタスク処理ロジックを実装します。

  1. corePoolSize タスクがある場合、タスクごとに新しいスレッドが作成されます。
  2. さらにタスクがある場合は、それらをタスク キューに入れて、すべてのスレッドが取得できるようにします。キューがいっぱいの場合は、一時的なスレッドを作成します
  3. スレッドの総数がmaximumPoolSizeに達すると、タスクをタスクキューに入れようとし続けます。
  4. バッファキューもいっぱいの場合、挿入は失敗し、拒否戦略が実行されます。

Tomcat と JDK スレッド プールの違いは、ステップ 3 にあります。スレッドの総数が最大数に達すると、Tomcat はすぐに拒否戦略を実行せず、タスク キューにタスクを追加しようとします。追加が失敗すると、拒否戦略が再度実行されます

具体的にはどのように実現されるのでしょうか?

public void execute(実行可能なコマンド、長いタイムアウト、TimeUnit 単位) {
    送信されたCountを増加して取得します。
    試す {
        //タスクを実行するには、JDK ネイティブ スレッド プールの execute 関数を呼び出します。super.execute(command);
    } キャッチ (RejectedExecutionException rx) {
       // スレッドの総数がmaximumPoolSizeに達すると、JDKネイティブスレッドプールはデフォルトの拒否ポリシーを実行します。if (super.getQueue() instanceof TaskQueue) {
            最終的な TaskQueue キュー = (TaskQueue)super.getQueue();
            試す {
                // タスクをタスクキューに追加し続ける if (!queue.force(command, timeout, unit)) {
                    送信されたCountを減分して取得します。
                    // バッファ キューがまだいっぱいの場合、挿入は失敗し、拒否戦略が実行されます。
                    新しい RejectedExecutionException("...") をスローします。
                }
            } 
        }
    }
}

タスクキューのカスタマイズ

Tomcat スレッド プールの execute メソッドの最初の行:

送信されたCountを増加して取得します。

タスクが失敗して例外がスローされると、カウンターは 1 つ減ります。

送信されたCountを減分して取得します。

Tomcat スレッド プールは、 submittedCount変数を使用して、スレッド プールに送信されたが実行されていないタスクの数を維持します。

なぜそのような変数を維持するのでしょうか?

Tomcat のタスク キュー TaskQueue は、JDK の LinkedBlockingQueue を拡張します。Tomcat はこれに容量を与え、それを親クラス LinkedBlockingQueue のコンストラクターに渡します。

パブリッククラスTaskQueueはLinkedBlockingQueue<Runnable>を拡張します。

  パブリックタスクキュー(int 容量) {
      スーパー(容量);
  }
  ...
}

容量パラメータは Tomcat の maxQueueSize パラメータによって設定されますが、maxQueueSize のデフォルト値はInteger.MAX_VALUE です。現在のスレッド数がコア スレッド数に達した後、別のタスクがある場合、スレッド プールはタスクをタスク キューに追加し、常に成功するので、新しいスレッドを作成する機会はありません。

この問題を解決するために、TaskQueue は LinkedBlockingQueue#offer を書き換え、適切なタイミングでタスクの追加が失敗したことを示す false を返します。このとき、スレッド プールは新しいスレッドを作成します。

適切な時期はいつですか?

パブリッククラスTaskQueueはLinkedBlockingQueue<Runnable>を拡張します。

  ...
   @オーバーライド
  // スレッドプールがタスクキューメソッドを呼び出すと、現在のスレッド数 > コアスレッド数 public boolean offer(Runnable o) {

      // スレッド数が最大に達した場合、新しいスレッドは作成できず、(parent.getPoolSize() == parent.getMaximumPoolSize()) の場合にのみタスク キューに追加できます。 
          super.offer(o) を返します。
          
      // これまでのところ、最大スレッド数 > 現在のスレッド数 > コアスレッド数であることを示しています // 新しいスレッドを作成できることを示しています:
      
      // 1. 送信されたタスクの数 < 現在のスレッド数の場合 // アイドル状態のスレッドがまだ存在し、新しいスレッドを作成する必要がないことを意味します if (parent.getSubmittedCount()<=(parent.getPoolSize())) 
          super.offer(o) を返します。
          
      // 2. 送信されたタスクの数 > 現在のスレッド数の場合 // スレッドが足りない場合は、false を返して新しいスレッドを作成します if (parent.getPoolSize()<parent.getMaximumPoolSize()) 
          false を返します。
          
      // デフォルトでは、タスクは常にタスクキューに入れられます。 return super.offer(o);
  }
  
}

したがって、Tomcat は、タスク キューの長さが無限の場合にスレッド プールが新しいスレッドを作成できるように、送信されたタスクの数を維持します。

Tomcat が JDK ネイティブ スレッド プールのバグを修正する方法について説明したこの記事はこれで終わりです。Tomcat JDK ネイティブ スレッド プールの詳細については、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Tomcatはスレッドプールを使用してリモート同時リクエストを処理します。
  • Tomcat ベースの接続数とスレッドプールの詳細な説明
  • JDK スレッド プールと Spring スレッド プールの使用例の分析
  • jdk独自のスレッドプールインスタンスの詳細な説明

<<:  ウェブサイトの速度を上げる6つの方法

>>:  Vue での props の使い方の紹介

推薦する

MySQL の昇順および降順データソートの実装

データの昇順、降順ソート1. フィールド名による単一フィールドのソート順機能:どのフィールドを基準に...

Docker で SVN サーバーを構築するチュートリアル

SVN は Subversion の略称で、ブランチ管理システムを使用して効率的に管理するオープンソ...

VMware15.5でcentos8.1をインストールし、物理メモリが不足する問題に対処する最も完全なチュートリアル

1. 仮想マシンの準備1. 新しい仮想マシンを作成する 2. 仮想マシンのカスタマイズを選択する 3...

JavaScript イベント委任の原則

目次1. イベント委任とは何ですか? 2. イベント委任の原則3. イベント委託の役割1. イベント...

組み込み Linux 開発環境で ping と nfs を構築するためのソリューション

1. 組み込みソフトウェアレベル 1) ブートローダ -> ブートローダ組み込みシステム全体の...

MySQLの日付加算と減算関数の詳細な説明

1. 追加時間()指定した秒数を日付に追加する select addtime(now(),1); -...

MySQL の重要なログファイルの包括的なインベントリ

目次導入ログ分類パラメータファイルエラーログファイル完全なログファイルスロークエリログバイナリログフ...

見落としがちなMySQLのCOLLATIONの例の詳細な説明

序文MySQL データベースの文字列型は、CHAR、VARCHAR、BINARY、BLOB、TEXT...

JavaScript の新しい要素トラバーサルプロパティを使用して子要素をトラバースする方法を学びます

目次1. ChildNodes属性のトラバーサル2. 要素シリーズ属性のトラバーサル以前は、chil...

誤って削除されたデータを復元するための mysqlbinlog コマンドを使用した mysql の実装

実験環境: MYSQL 5.7.22バイナリログを有効にするログ形式 MIXED実験プロセス: 1....

JSのアンカーリンクをクリックするとスムーズにスクロールし、自由にトップ位置に調整できます。

アンカーリンクをクリックするとスムーズにスクロールし、自由にトップ位置に調整できます。 1. アンカ...

jQueryはフォーム検証機能を実装します

jQuery フォーム検証の例 / ユーザー名、パスワード、住所、電子メールの検証を含む下記の通り ...

メタ宣言注釈の手順

メタ宣言注釈の手順: 1. モバイル ページと 1 対 1 で対応するすべての PC ページを分類し...

HTML+CSS ボックスモデルの例 (円、半円など) 「border-radius」はシンプルで使いやすい

多くの友人は、フロントエンドを学習するときに、ボックス モデルがデフォルトで正方形であることに気付き...

長いデータを HTML で表示するときに処理する方法

HTML で長いデータを表示する場合、マウスをその上に移動するとデータを切り捨てて完全なデータを表示...