導入
マルチスレッドは、初期の頃から Java に組み込まれていました。それでは、マルチスレッドとは何なのかを簡単に見てみましょう。
Oracle の公式レッスン「レッスン: 「Hello World!」アプリケーション」を出発点として見てみましょう。Hello World アプリケーションのコードを次のように少し変更してみましょう。
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello, " + args[0]);
}
}
args
プログラムの開始時に渡される入力パラメータの配列です。このコードを、クラス名と拡張子 に一致する名前のファイルに保存しましょう
.java
。
javacユーティリティを使用してコンパイルしましょう。
javac HelloWorldApp.java
その後、いくつかのパラメータを使用してコードを呼び出します。たとえば、Roger:
java HelloWorldApp Roger
私たちのコードには重大な欠陥があります。引数を渡さない場合 (つまり、java HelloWorldApp を実行するだけ)、エラーが発生します。
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at HelloWorldApp.main(HelloWorldApp.java:3)
という名前のスレッドで例外 (つまり、エラー) が発生しました
main
。Java にはある種のスレッドがあることがわかりました。ここから私たちの旅が始まります。
Javaとスレッド
スレッドとは何かを理解するには、Java アプリケーションがどのように起動されるかを理解する必要があります。コードを次のように変更してみましょう。
class HelloWorldApp {
public static void main(String[] args) {
while (true) {
}
}
}
それでは、javac を使用して再度コンパイルしてみましょう。次に、便宜上、Java コードを別のウィンドウで実行します。Windows では、次のようにこれを行うことができます
start java HelloWorldApp
。ここで、
jpsユーティリティを使用して、Java がどのような情報を表示するかを見てみましょう。
最初の数字は PID またはプロセス ID、プロセス識別子です。プロセスとは何ですか?
Процесс — это совокупность codeа и данных, разделяющих общее виртуальное addressное пространство.
プロセスの助けを借りて、さまざまなプログラムの実行は互いに分離されます。各アプリケーションは、他のプログラムに干渉することなく独自のメモリ領域を使用します。記事「
https://habr.com/post/164487/」をさらに詳しく読むことをお勧めします。プロセスはスレッドなしでは存在できないため、プロセスが存在する場合は、そのプロセス内に少なくとも 1 つのスレッドが存在します。Java ではどのようにしてこれが起こるのでしょうか? Java プログラムを実行するとき、その実行は
main
. 私たちはプログラムに入るようなものなので、この特別な方法は
main
エントリーポイント、または「エントリーポイント」と呼ばれます。このメソッドは、
main
常に
public static void
Java 仮想マシン (JVM) がプログラムの実行を開始できるようにする必要があります。詳細については、「
Java の main メソッドが静的であるのはなぜですか? 」を参照してください。Java ランチャー (java.exe または javaw.exe) は単純なアプリケーション (単純な C アプリケーション) であることがわかります。実際には JVM であるさまざまな DLL をロードします。Java ランチャーは、特定の Java Native Interface (JNI) 呼び出しのセットを作成します。JNI は、Java 仮想マシンの世界と C++ の世界の橋渡しをするメカニズムです。ランチャーは JVM ではなく、そのローダーであることがわかりました。JVM を起動するために実行する正しいコマンドを認識します。JNI 呼び出しを使用して必要なすべての環境を編成する方法を知っています。この環境の編成には、通常 と呼ばれるメイン スレッドの作成も含まれます
main
。Java プロセス内にどのスレッドが存在するかをより明確に確認するには、JDK に含まれる
jvisualvmプログラムを使用します。プロセスの pid がわかれば、そのデータをすぐに開くことができます。
jvisualvm --openpid айдипроцесса
興味深いことに、各スレッドには、プロセスに割り当てられたメモリ内に独自の個別の領域があります。このメモリ構造はスタックと呼ばれます。スタックはフレームで構成されます。フレームはメソッドを呼び出すポイント、つまり実行ポイントです。フレームは StackTraceElement として表すこともできます (
StackTraceElementの Java API を参照)。
各スレッドに割り当てられるメモリの詳細については、こちらをご覧ください。
Java APIを見てThread という単語を検索すると、
java.lang.Threadクラスがあることがわかります。Java でストリームを表すのはこのクラスであり、これを使用して作業する必要があります。
java.lang.スレッド
Java のスレッドは、クラスのインスタンスとして表されます
java.lang.Thread
。Java の Thread クラスのインスタンスはそれ自体がスレッドではないことをすぐに理解する価値があります。これは、JVM とオペレーティング システムによって管理される低レベル スレッド用の API の一種にすぎません。Java ランチャーを使用して JVM を起動すると、名前付きのメイン スレッド
main
とさらにいくつかのサービス スレッドが作成されます。Thread クラスの JavaDoc に記載されているように、
When a Java Virtual Machine starts up, there is usually a single non-daemon thread
スレッドにはデーモンと非デーモンの 2 種類があります。デーモン スレッドは、バックグラウンドで何らかの作業を実行するバックグラウンド スレッド (サービス スレッド) です。この興味深い用語は「マクスウェルの悪魔」への言及であり、ウィキペディアの「
悪魔」に関する記事で詳細を読むことができます。ドキュメントに記載されているように、JVM は次の状態になるまでプログラム (プロセス) を実行し続けます。
- Runtime.exitメソッドが呼び出されない
- デーモン以外のすべてのスレッドが作業を完了しました (エラーも例外もスローされませんでした)
したがって、重要な点は、実行中のどのコマンドでもデーモン スレッドを終了できるということです。したがって、それらのデータの完全性は保証されません。したがって、デーモン スレッドは一部のサービス タスクに適しています。たとえば、Java には、ファイナライズ メソッドの処理を担当するスレッドや、ガベージ コレクター (GC) に関連するスレッドがあります。各スレッドは何らかのグループ (
ThreadGroup ) に属します。そして、グループが互いに入り込んで、何らかの階層や構造を形成することがあります。
public static void main(String []args){
Thread currentThread = Thread.currentThread();
ThreadGroup threadGroup = currentThread.getThreadGroup();
System.out.println("Thread: " + currentThread.getName());
System.out.println("Thread Group: " + threadGroup.getName());
System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
グループを使用すると、フローの管理を合理化し、フローを追跡できます。グループに加えて、スレッドには独自の例外ハンドラーがあります。例を見てみましょう:
public static void main(String []args) {
Thread th = Thread.currentThread();
th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("An error occurred: " + e.getMessage());
}
});
System.out.println(2/0);
}
ゼロによる除算はエラーを引き起こし、ハンドラーによって捕捉されます。ハンドラーを自分で指定しない場合は、デフォルトのハンドラー実装が機能し、StdError にエラー スタックが表示されます。詳細については、レビュー
http://pro-java.ru/java-dlya-opytnyx/obrabotchik-neperexvachennyx-isklyuchenij-java/を参照してください。さらに、スレッドには優先順位があります。優先順位について詳しくは、記事「
マルチスレッドにおける Java スレッドの優先順位」。
スレッドの作成
ドキュメントに記載されているように、スレッドを作成するには 2 つの方法があります。1つ目は後継者を作ることです。例えば:
public class HelloWorld{
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello, World!");
}
}
public static void main(String []args){
Thread thread = new MyThread();
thread.start();
}
}
ご覧のとおり、タスクはメソッドで起動され
run
、スレッドはメソッドで起動されます
start
。彼らを混乱させるべきではありません。なぜなら... メソッドを
run
直接実行すると、新しいスレッドは開始されません。
start
これは、JVM に新しいスレッドの作成を要求するメソッドです。Thread の子孫を使用するオプションは、クラス階層に Thread を含めるので不適切です。2 番目の欠点は、「単独責任」の原則に確実に違反し始めていることです。私たちのクラスは、スレッドの管理と、このスレッドで実行する必要があるタスクの両方を同時に担当します。どちらが正しい?
run
答えは、オーバーライドする メソッドそのものにあります。
public void run() {
if (target != null) {
target.run();
}
}
以下に、クラスのインスタンスを作成するときに Thread に渡すことができる
target
いくつかの を示します。
java.lang.Runnable
したがって、次のようにすることができます。
public class HelloWorld{
public static void main(String []args){
Runnable task = new Runnable() {
public void run() {
System.out.println("Hello, World!");
}
};
Thread thread = new Thread(task);
thread.start();
}
}
Runnable
これはJava 1.8 以降の関数インターフェース でもあります。これにより、スレッドのタスク コードをさらに美しく記述できるようになります。
public static void main(String []args){
Runnable task = () -> {
System.out.println("Hello, World!");
};
Thread thread = new Thread(task);
thread.start();
}
合計
したがって、この話から、ストリームとは何か、ストリームがどのように存在し、ストリームを使用してどのような基本操作を実行できるかが明確になることを願っています。
次のパートでは、スレッドがどのように相互作用するか、またスレッドのライフサイクルが何であるかを理解する価値があります。#ヴィアチェスラフ
GO TO FULL VERSION