Java のマルチスレッドについておそらく知らなかった 5 つのこと
出典: DZone スレッドは Java プログラミング言語の中心です。Hello World プログラムを実行する場合でも、メインスレッドが必要です。アプリケーション コードをより機能的かつパフォーマンス的にしたい場合は、必要に応じて他のスレッドをプログラムに追加できます。Web サーバーについて話している場合、Web サーバーは何百ものリクエストを同時に処理します。これには複数のスレッドが使用されます。 スレッドは間違いなく便利ですが、多くの開発者にとってスレッドを使用するのは難しい場合があります。この記事では、新人開発者や経験豊富な開発者が知らないかもしれない 5 つのマルチスレッドの概念を紹介します。1. プログラムの順序と実行順序が一致しない
コードを書くとき、私たちはコードが書いたとおりに実行されることを前提としています。しかし、実際にはそうではありません。Java コンパイラは、シングルスレッド コードで出力が変更されないと判断できる場合、実行順序を変更して最適化することがあります。次のコード スニペットを見てください。package ca.bazlur.playground;
import java.util.concurrent.Phaser;
public class ExecutionOrderDemo {
private static class A {
int x = 0;
}
private static final A sharedData1 = new A();
private static final A sharedData2 = new A();
public static void main(String[] args) {
var phaser = new Phaser(3);
var t1 = new Thread(() -> {
phaser.arriveAndAwaitAdvance();
var l1 = sharedData1;
var l2 = l1.x;
var l3 = sharedData2;
var l4 = l3.x;
var l5 = l1.x;
System.out.println("Thread 1: " + l2 + "," + l4 + "," + l5);
});
var t2 = new Thread(() -> {
phaser.arriveAndAwaitAdvance();
var l6 = sharedData1;
l6.x = 3;
System.out.println("Thread 2: " + l6.x);
});
t1.start();
t2.start();
phaser.arriveAndDeregister();
}
}
このコードは簡単そうに見えます。2 つのスレッドを使用する2 つの共有データ インスタンス ( sharedData1とsharedData2 ) があります。コードを実行すると、次のような出力が期待されます。
スレッド 2: 3 スレッド 1: 0,0,0
ただし、コードを数回実行すると、異なる結果が表示されます。
スレッド 2: 3 スレッド 1: 3,0,3 スレッド 2: 3 スレッド 1: 0,0,3 スレッド 2: 3 スレッド 1: 3,3,3 スレッド 2: 3 スレッド 1: 0,3,0 スレッド 2 : 3 スレッド 1: 0,3,3
これらすべてのストリームがあなたのマシン上でまったく同じように再生されるとは言いませんが、その可能性は十分にあります。
2. Java スレッドの数には制限があります
Java でスレッドを作成するのは簡単です。ただし、好きなだけ作成できるという意味ではありません。スレッドの数には制限があります。次のプログラムを使用すると、特定のマシン上に作成できるスレッドの数を簡単に調べることができます。package ca.bazlur.playground;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
public class Playground {
public static void main(String[] args) {
var counter = new AtomicInteger();
while (true) {
new Thread(() -> {
int count = counter.incrementAndGet();
System.out.println("thread count = " + count);
LockSupport.park();
}).start();
}
}
}
上記のプログラムは非常に単純です。ループ内にスレッドを作成してからそれをパークします。つまり、スレッドは将来の使用のために無効になりますが、システム コールを実行してメモリを割り当てます。プログラムはスレッドを作成し続け、それ以上作成できなくなると例外をスローします。私たちは、プログラムが例外をスローするまで受け取る数値に興味があります。私のコンピュータでは 4065 スレッドしか作成できませんでした。
3. スレッドが多すぎるとパフォーマンスが向上する保証はありません
Java でスレッドを簡単にすればアプリケーションのパフォーマンスが向上すると考えるのは単純です。残念ながら、この仮定は、Java が現在提供している従来のマルチスレッド モデルでは間違っています。実際、スレッドが多すぎると、アプリケーションのパフォーマンスが低下する可能性があります。まず次の質問をしてみましょう。アプリケーションのパフォーマンスを最大化するために作成できるスレッドの最適な最大数はいくつですか? そうですね、答えはそれほど単純ではありません。それは私たちが行う仕事の種類によって大きく異なります。複数の独立したタスクがあり、そのすべてが計算処理であり、外部リソースをブロックしていない場合、スレッドの数が多くてもパフォーマンスはあまり向上しません。一方、8 コア プロセッサの場合、最適なスレッド数は (8 + 1) になります。このような場合、Java 8 で導入された並列スレッドに頼ることができます。デフォルトでは、並列スレッドは共有 Fork/Join プールを使用します。使用可能なプロセッサの数と同じスレッドが作成されます。これはプロセッサが集中的に動作するのに十分です。何もブロックされていない CPU 負荷の高いジョブにスレッドを追加しても、パフォーマンスは向上しません。むしろ、資源を無駄に浪費するだけです。 注記。余分なスレッドを用意する理由は、計算集約型のスレッドであっても、ページ フォールトが発生したり、他の理由で中断されたりする場合があるためです。(参照: Java Parallelism in Practice、Brian Goetz、170 ページ) ただし、たとえば、タスクが I/O バウンドであると仮定します。この場合、スレッドは外部通信 (データベース、他の API など) に依存するため、スレッドの数を増やすのが合理的です。その理由は、スレッドが Rest API で待機しているときに、他のスレッドが動作を継続できるためです。さて、このような場合にスレッドの数が多すぎるともう一度尋ねることができます。場合によります。すべてのケースに適合する完璧な数字はありません。したがって、適切なテストを行って、特定のワークロードとアプリケーションに最適なものを見つける必要があります。最も典型的なシナリオでは、通常、タスクのセットが混在しています。そしてそのような場合、物事は完了します。Brian Goetz 氏は、著書『Java Concurrency in Practice』の中で、ほとんどの場合に使用できる公式を提案しました。 スレッド数 = 使用可能なコアの数 * (1 + 待機時間 / サービス時間) 待機時間には、HTTP 応答の待機、ロックの取得などの IO が含まれます。 サービスタイム(サービス時間) は、HTTP 応答の処理、マーシャリング/アンマーシャリングなどの計算時間です。たとえば、アプリケーションは API を呼び出して処理します。アプリケーション サーバーに 8 つのプロセッサがあり、平均 API 応答時間が 100 ミリ秒、応答処理時間が 20 ミリ秒である場合、理想的なスレッド サイズは次のようになります。
N = 8 * ( 1 + 100/20) = 48
ただし、これは単純化しすぎです。数値を決定するには適切なテストが常に重要です。
4. マルチスレッド化は並列処理ではありません
マルチスレッドと並列処理を同じ意味で使用することがありますが、これはもはや完全に関連しません。Java ではスレッドを使用して両方を実現しますが、それらは 2 つの異なるものです。「プログラミングにおいて、マルチスレッドは実行中のプロセスに関係なく特殊なケースであり、並列処理は(おそらく関連する)計算を同時に実行することです。マルチスレッドとは、同時に多くのものと対話することです。並行性は多くのことを同時に実行します。」ロブ・パイクによる上記の定義は非常に正確です。完全に独立したタスクがあり、それらを別々に計算できるとします。この場合、これらのタスクは並列と呼ばれ、フォーク/結合プールまたは並列スレッドで実行できます。一方、多くのタスクがある場合、そのうちのいくつかは他のタスクに依存する可能性があります。この構成と構造の方法はマルチスレッドと呼ばれます。それは構造と関係があります。特定の結果を達成するために、必ずしも 1 つを早く完了する必要はなく、複数のタスクを同時に実行したい場合があります。5. Project Loom を使用すると、何百万ものスレッドを作成できます
前のポイントでは、スレッドの数が増えてもアプリケーションのパフォーマンスが向上するわけではないと主張しました。しかし、マイクロサービスの時代では、特定の作業を実行するにはあまりにも多くのサービスとやり取りする必要があります。このようなシナリオでは、ほとんどの時間、スレッドはブロックされた状態のままになります。最新の OS は何百万ものオープンソケットを処理できますが、スレッド数には制限があるため、多くの通信チャネルを開くことはできません。しかし、何百万ものスレッドを作成し、それぞれがオープン ソケットを使用して外部と通信する場合はどうなるでしょうか? これにより、アプリケーションのスループットが確実に向上します。この考えをサポートするために、Java には Project Loom と呼ばれる取り組みがあります。これを使用すると、何百万もの仮想スレッドを作成できます。たとえば、次のコード スニペットを使用すると、マシン上に 450 万のスレッドを作成できました。import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
public class Main {
public static void main(String[] args) {
var counter = new AtomicInteger();
// 4_576_279
while (true) {
Thread.startVirtualThread(() -> {
int count = counter.incrementAndGet();
System.out.println("thread count = " + count);
LockSupport.park();
});
}
}
}
このプログラムを実行するには、Java 18 がインストールされている必要があります。Java 18 はここから ダウンロードできます。次のコマンドを使用してコードを実行できます: java --source 18 --enable-preview Main.java
技術的負債と戦うための 10 の JetBrains 拡張機能
出典: DZone 多くの開発チームは、期限を守らなければならないという大きなプレッシャーを感じています。このため、多くの場合、コードベースを修正してクリーンアップする十分な時間がありません。このような状況では、技術的負債が急速に蓄積することがあります。エディター拡張機能は、この問題の解決に役立ちます。技術的負債と戦うための 10 の最高の JetBrains 拡張機能 (Java サポート付き) を見てみましょう。リファクタリングおよび技術的負債ツール
1.リファクタリングインサイト
RefactorInsight は、リファクタリングに関する情報を提供することで、IDE でのコード変更の可視性を向上させます。- この拡張機能は、マージ リクエストのリファクタリングを定義します。
- リファクタリングを含むコミットをマークします。
- 「Git Log」タブで選択した特定のコミットのリファクタリングを表示するのに役立ちます。
- クラス、メソッド、フィールドのリファクタリング履歴を表示します。
2. IDE のステップサイズ問題トラッカー
Stepsize は、開発者にとって優れた問題追跡ツールです。この拡張機能は、エンジニアがより良い TODO やコード コメントを作成するだけでなく、技術的負債やリファクタリングなどの優先順位を付けるのにも役立ちます。- Stepsize を使用すると、エディターで直接コード内のタスクを作成および表示できます。
- 取り組んでいる機能に影響を与える問題を見つけます。
- Jira、Asana、Linear、Azure DevOps、GitHub の統合を使用して、スプリントに課題を追加します。
3. New Relic コードストリーム
New Relic CodeStream は、コードについて議論したりレビューしたりするための開発者コラボレーション プラットフォームです。GitHub、BitBucket、GitLab からのプル リクエスト、Jira、Trello、Asana、その他 9 つからの課題管理をサポートし、コード ディスカッションを提供してすべてを結び付けます。- GitHub でプル リクエストを作成、レビュー、マージします。
- 暫定的なコードレビューで進行中の作業に関するフィードバックを取得します。
- コードの問題についてチームメイトと話し合う。
TODOとコメント
4.コメントハイライト
このプラグインを使用すると、コメント行と言語キーワードのカスタム強調表示を作成できます。このプラグインには、コメント行を強調表示するためのカスタム トークンを定義する機能もあります。5.より良いコメント
Better Comments 拡張機能は、コード内でより明確なコメントを作成するのに役立ちます。この拡張機能を使用すると、注釈を次のように分類できるようになります。- アラート。
- リクエスト。
- TODO。
- 基本的な瞬間。
バグとセキュリティの脆弱性
6.ソナーリント_
SonarLint を使用すると、コードの問題が発生する前にトラブルシューティングを行うことができます。スペルチェッカーとしても使用できます。SonarLint は、コードを作成する際にバグやセキュリティの脆弱性を強調表示し、明確な修復手順を提供するので、コードがコミットされる前にそれらを修正できます。7.スポットバグ
SpotBugs プラグインは、IntelliJ IDEA の Java コードのバグを見つけるための静的バイトコード分析を提供します。SpotBugs は Java 用の欠陥検出ツールであり、静的分析を使用して、null ポインターの逆参照、無限再帰ループ、Java ライブラリの誤用、デッドロックなどの 400 を超えるバグ パターンを検出します。SpotBugs は、大規模なアプリケーションで数百の重大な欠陥を特定できます (通常、コメントされていない生のステートメントの 1000 ~ 2000 行につき約 1 つの欠陥)。8. Snyk脆弱性スキャナー
Snyk の脆弱性スキャナーは、プロジェクト内のセキュリティ脆弱性とコード品質の問題を見つけて修正するのに役立ちます。- セキュリティ上の問題を見つけて修正します。
- カテゴリに分類されたさまざまな種類の問題のリストを表示します。
- トラブルシューティングのヒントを表示します。
GO TO FULL VERSION