English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Javaはマルチスレッドプログラミングに組み込みのサポートを提供しています。 スレッドはプロセス内の単一の順序の制御フローを指し、プロセス内で複数のスレッドが同時に実行され、各スレッドが異なるタスクを実行します。
マルチスレッドは多任務の特別な形式ですが、マルチスレッドはより少ないリソースコストを使用します。
ここでは、スレッドに関連するもう一つの用語を定義しています。 - プロセス:プロセスはオペレーティングシステムが割り当てたメモリ空間を含み、1つまたは複数のスレッドを含んでいます。スレッドは独立して存在することはできません。スレッドはプロセスの一部でなければなりません。プロセスはすべての非デーモンスレッドが終了するまで実行し続けます。
マルチスレッドは、CPUを最大限に活用するために高効率なプログラムを書くプログラマーに役立ちます。
スレッドは動的な実行プロセスであり、生成から死亡までのプロセスもあります。
以下はスレッドの完全なライフサイクルを示す図です。
新建状態:
を使用して new キーワードと Thread クラスやそのサブクラスがスレッドオブジェクトを作成すると、そのスレッドオブジェクトは新建状態にいます。この状態は、プログラムが終了するまで続きます。 start() このスレッド。
就緒状態:
スレッドオブジェクトがstart()メソッドを呼び出された後、そのスレッドは就緒状態に入ります。就緒状態のスレッドは就緒キューに位置し、JVMのスレッドスケジューラのスケジューリングを待ちます。
実行状態:
就緒状態のスレッドがCPUリソースを取得すると、実行できます run()この時点でスレッドは実行状態にいます。実行状態のスレッドは最も複雑であり、ブロック状態、就緒状態、死亡状態に変化することができます。
ブロック状態:
スレッドがsleep(睡眠)、suspend(一時停止)などのメソッドを実行し、占有しているリソースを失った後、そのスレッドは実行状態からブロック状態に入ります。睡眠時間が経過したり、デバイスリソースを取得した後は再び就緒状態に入ることができます。これは3つのカテゴリに分けることができます:
待機ブロック:実行状態のスレッドがwait()メソッドを実行し、スレッドが待機ブロック状態に入ります。
同期ブロック:スレッドがsynchronized同期ロックを取得に失敗した場合(他のスレッドが同期ロックを占有しているため)。
他のブロック:スレッドのsleep()やjoin()を呼び出してI/Oリクエストが発生すると、スレッドはブロック状態に入ります。sleep()状態がタイムアウトしたり、join()がスレッドの終了を待つかタイムアウトしたり、またはI/O処理が完了し、スレッドは再び就緒状態に戻ります。
死亡状態:
実行状態のスレッドがタスクを完了したり、他の終了条件が発生した場合、そのスレッドは終了状態に切り替わります。
各Javaスレッドには優先度があり、これによりオペレーティングシステムがスレッドのスケジューリング順序を決定するのに役立ちます。
Javaのスレッドの優先度は整数であり、その値の範囲は 1 (Thread.MIN_PRIORITY)。 - 10 (Thread.MAX_PRIORITY)。
デフォルトでは、各スレッドに優先度 NORM_PRIORITYが割り当てられます(5)。
高い優先度を持つスレッドはプログラムにとってより重要であり、低優先度のスレッドよりも前にプロセッサリソースが割り当てられるべきです。ただし、スレッドの優先度はスレッドの実行順序を保証することはできず、非常にプラットフォーム依存性が高いです。
Javaはスレッドを作成するための3つの方法を提供しています:
Runnableインターフェースを実装することで;
Threadクラスを継承することで;
CallableとFutureを使用してスレッドを作成する
スレッドを作成する最も簡単な方法は、Runnableインターフェースを実装したクラスを作成することです。
Runnableを実装するためには、クラスが run() メソッドを実行するだけで十分です。以下のように宣言されます:
public void run()
このメソッドをオーバーライドすることもできますが、重要なのは run() メソッドが他のメソッドを呼び出し、他のクラスを使用し、変数を宣言できることです。
Runnableインターフェースを実装したクラスを作成した後、そのクラスでスレッドオブジェクトをインスタンス化することができます。
Threadはいくつかのコンストラクタを定義しており、以下はよく使用されるものです:
Thread(Runnable threadOb,String threadName);
ここでは、threadObはRunnableインターフェースを実装したクラスのインスタンスの例であり、threadNameは新しいスレッドの名前を指定しています。
新しいスレッドが作成された後、start()メソッドを呼び出すことで実行されます。
void start();
以下は、スレッドを作成し実行を開始する例です:
class RunnableDemo implements Runnable { private Thread t; private String threadName; RunnableDemo( String name) { threadName = name; System.out.println("Creating ")} + threadName); } public void run() { System.out.println("Running "); + threadName); try { for(int i = 4; i > 0; i--) { System.out.println("Thread: "); + threadName + ", " + i); // スレッドを少し休止させます。 Thread.sleep(50); } } catch (InterruptedException e) { System.out.println("Thread "); + threadName + " interrupted."); } System.out.println("Thread "); + threadName + " exiting."); } public void start() { System.out.println("Starting "); + threadName); if (t == null) { t = new Thread(this, threadName); t.start(); } } } public class TestThread { public static void main(String args[]) { RunnableDemo R1 = new RunnableDemo( "Thread-1"); R1.start(); RunnableDemo R2 = new RunnableDemo( "Thread-2"); R2.start(); } }
以下に上記プログラムの実行結果を示します:
Creating Thread-1 Starting Thread-1 Creating Thread-2 Starting Thread-2 Running Thread-1 Thread: Thread-1, 4 Running Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting.
スレッドを生成する第二种方法は、Threadクラスを継承する新しいクラスを作成し、そのクラスのインスタンスを作成することです。
継承クラスは run() メソッドをオーバーライドする必要があり、これは新しいスレッドのエントリーポイントです。また、実行するために start() メソッドを呼び出す必要があります。
このメソッドは多スレッド実装方法の一つとしてリストされていますが、本質的には Runnable インターフェースを実装したサンプルです。
class ThreadDemo extends Thread { private Thread t; private String threadName; ThreadDemo( String name) { threadName = name; System.out.println("Creating ")} + threadName); } public void run() { System.out.println("Running "); + threadName); try { for(int i = 4; i > 0; i--) { System.out.println("Thread: "); + threadName + ", " + i); // スレッドを少し休止させます。 Thread.sleep(50); } } catch (InterruptedException e) { System.out.println("Thread "); + threadName + " interrupted."); } System.out.println("Thread "); + threadName + " exiting."); } public void start() { System.out.println("Starting "); + threadName); if (t == null) { t = new Thread(this, threadName); t.start(); } } } public class TestThread { public static void main(String args[]) { ThreadDemo T1 = new ThreadDemo("Thread-1"); T1.start(); ThreadDemo T2 = new ThreadDemo("Thread-2"); T2.start(); } }
以下に上記プログラムの実行結果を示します:
Creating Thread-1 Starting Thread-1 Creating Thread-2 Starting Thread-2 Running Thread-1 Thread: Thread-1, 4 Running Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting.
以下に Thread クラスの重要なメソッドの一覧を示します:
番号 | メソッドの説明 |
---|---|
1 | public void start() このスレッドの実行を開始します。;Java 仮想機がこのスレッドの run メソッドを呼び出します。 |
2 | public void run() このスレッドが独立した Runnable 実行オブジェクトで構築されている場合、その Runnable オブジェクトの run メソッドを呼び出します;そうでない場合、このメソッドは何も行わずに返します。 |
3 | public final void setName(String name) スレッドの名前を変更し、それが引数 name と同じになります。 |
4 | public final void setPriority(int priority) スレッドの優先度を変更します。 |
5 | public final void setDaemon(boolean on) このスレッドをデフォルトスレッドまたはユーザースレッドにマークします。 |
6 | public final void join(long millisec) このスレッドが終了するまで待機する時間は最長millisミリ秒です。 |
7 | public void interrupt() スレッドを中断します。 |
8 | public final boolean isAlive() スレッドがアクティブであるかどうかをテストします。 |
スレッドがアクティブであるかどうかをテストします。上記のメソッドはThreadオブジェクトによって呼び出されます。以下のメソッドはThreadクラスの静的メソッドです。
番号 | メソッドの説明 |
---|---|
1 | public static void yield() 現在実行中のスレッドオブジェクトを一時停止し、他のスレッドを実行します。 |
2 | public static void sleep(long millisec) 指定されたミリ秒数内に現在実行中のスレッドをスリープ(一時停止)します。この操作はシステムのカウンタとスケジューラの精度と正確性に影響されます。 |
3 | public static boolean holdsLock(Object x) 現在のスレッドが指定されたオブジェクトに対して監視器ロックを保持している場合にのみtrueを返します。 |
4 | public static Thread currentThread() 現在実行中のスレッドオブジェクトの参照を返します。 |
5 | public static void dumpStack() 現在のスレッドのスタックトレースを標準エラーストリームに印刷します。 |
以下のThreadClassDemoプログラムはThreadクラスの一部のメソッドを示しています:
// ファイル名 : DisplayMessage.java // 通过实现 Runnable インターフェースでスレッドを生成 public class DisplayMessage implements Runnable { private String message; public DisplayMessage(String message) { this.message = message; } public void run() { while(true) { System.out.println(message); } } }
// ファイル名 : GuessANumber.java // 通过继承 Thread 类创建线程 public class GuessANumber extends Thread { private int number; public GuessANumber(int number) { this.number = number; } public void run() { int カウンタ = 0; int guess = 0; do { guess = (int) (Math.random() * 100 + 1); System.out.println(this.getName() + " guesses " + guess); カウンタ++; } while(guess != number); System.out.println("** 正解!" + this.getName() + "in" + カウンタ + "guesses.**"); } }
// ファイル名 : ThreadClassDemo.java public class ThreadClassDemo { public static void main(String [] args) { Runnable hello = new DisplayMessage("Hello"); Thread thread1 = new Thread(hello); thread1.setDaemon(true); thread1.setName("hello"); System.out.println("Starting hello thread..."); thread1.start(); Runnable bye = new DisplayMessage("Goodbye"); Thread thread2 = new Thread(bye); thread2.setPriority(Thread.MIN_PRIORITY); thread2.setDaemon(true); System.out.println("Starting goodbye thread..."); thread2.start(); System.out.println("Starting thread3..."); Thread thread3 = new GuessANumber(27); thread3.start(); try { thread3.join(); }catch(InterruptedException e) { System.out.println("Thread interrupted."); } System.out.println("Starting thread4..."); Thread thread4 = new GuessANumber(75); thread4.start(); System.out.println("main() is ending..."); } }
実行結果は以下の通り、各回の実行結果は異なります。
こんにちはスレッドの起動... さようならスレッドの起動... こんにちは こんにちは こんにちは こんにちは こんにちは こんにちは さようなら さようなら さようなら さようなら さようなら .......
1. Callable インターフェースの実現クラスを作成し、call() メソッドを実装します。この call() メソッドはスレッドの実行体として使用され、返り値があります。
2. Callable 実現クラスの例を作成し、FutureTask クラスを使用して Callable オブジェクトを包装し、FutureTask オブジェクトはその Callable オブジェクトの call() メソッドの返り値をエンケードします。
3. FutureTask オブジェクトを Thread オブジェクトの target として作成し、新しいスレッドを起動します。
4. 調用 FutureTask オブジェクトの get() メソッドで子スレッドの実行終了後の返り値を取得します。
public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); for(int i = 0;i < 100;i++) { System.out.println(Thread.currentThread().getName())+" 的循环变量i的值"+i); if(i==20) { new Thread(ft,"有返回值的线程").start(); } } try { System.out.println("子线程的返回值:")+ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } @Override public Integer call() throws Exception { int i = 0; for(;i<100;i++) { System.out.println(Thread.currentThread().getName())+" "+i); } return i; } }
1. Runnable、Callable インターフェースを実装してマルチスレッドを作成する方法では、スレッドクラスは Runnable インターフェースまたは Callable インターフェースを実装しており、他のクラスを継承することもできます。
2. Threadクラスを継承してマルチスレッドを作成する方法はシンプルで、現在のスレッドにアクセスする場合は Thread.currentThread() メソッドを使用する必要はありません。直接 this を使用して現在のスレッドを取得できます。
マルチスレッドプログラミングを行う際には、以下の概念を理解する必要があります:
スレッド同期
スレッド間通信
スレッド死锁
スレッド制御:一時停止、停止と復旧
マルチスレッドの効果的な利用の鍵は、プログラムが並行実行されていることを理解することです。例えば、プログラムに2つの並行実行が必要なサブシステムがある場合、マルチスレッドプログラミングを利用する必要があります。
マルチスレッドの使用により、非常に効率的なプログラムを書くことができます。ただし、多くのスレッドを作成すると、プログラムの実行効率は低下しますが、向上するわけではありません。
また、コンテキストの切り替えのコストも非常に重要です。もし多くのスレッドを作成すると、CPUがコンテキストの切り替えに費やす時間がプログラムの実行時間を超えることになります!