Java マルチスレッド プログラミング


Java には、マルチスレッド プログラミングのサポートが組み込まれています。マルチスレッド プログラムは、同時に実行できる 2 つ以上の部分で構成されます。プログラムの各部分はスレッドと呼ばれ、各スレッドは独立した実行パスを定義します。

マルチスレッドはマルチタスクの特殊な形式ですが、マルチスレッドでは使用するリソースのオーバーヘッドが小さくなります。

ここで、スレッドに関連する別の用語であるプロセスを定義します。プロセスには、オペレーティング システムによって割り当てられたメモリ空間が含まれ、1 つ以上のスレッドが含まれます。スレッドは独立して存在することはできず、プロセスの一部である必要があります。プロセスは、待機していないすべてのスレッドの実行が終了するまで実行されます。

マルチスレッドにより、プログラマーは CPU を最大限に活用する効率的なプログラムを作成できます。


スレッドのライフサイクル

スレッドは、そのライフサイクルのさまざまな段階を通過します。次の図は、スレッドの完全なライフサイクルを示しています。

java-thread.jpg

  • 新しい状態:

    newキーワードとThreadクラスまたはそのサブクラスを使用してスレッドオブジェクトを作成すると、スレッドオブジェクトは新しい状態になります。このスレッドのプログラム start() が実行されるまで、この状態が維持されます。

  • 準備完了状態:

    スレッドオブジェクトが start() メソッドを呼び出すと、スレッドは準備完了状態になります。準備完了状態のスレッドは準備完了キュー内にあり、JVM のスレッド スケジューラによるスケジューリングを待っています。

  • 実行状態:

    準備完了状態のスレッドがCPUリソースを取得すると、run()を実行でき、スレッドは実行状態になります。実行状態のスレッドは最も複雑で、ブロックされたり、準備完了になったり、停止したりする可能性があります。

  • ブロッキング状態:

    スレッドがスリープ(スリープ)、サスペンド(サスペンド)などのメソッドを実行すると、占有されていたリソースを失った後、スレッドは実行状態からブロッキング状態に入ります。スリープ時間が経過するか、デバイスのリソースが取得された後、準備完了状態に再度入ることができます。

  • 終了状態:

    実行状態のスレッドがタスクを完了するか、その他の終了条件が発生すると、スレッドは終了状態に切り替わります。


スレッドの優先順位

すべての Java スレッドには優先順位があり、オペレーティング システムがスレッドのスケジュール順序を決定するのに役立ちます。

Java スレッドの優先度は整数であり、その値の範囲は 1 (Thread.MIN_PRIORITY) ~ 10 (Thread.MAX_PRIORITY) です。

デフォルトでは、すべてのスレッドに優先度レベル NORM_PRIORITY (5) が割り当てられます。

優先度の高いスレッドはプログラムにとってより重要であるため、優先度の低いスレッドよりも先にプロセッサ リソースを割り当てる必要があります。ただし、スレッドの優先順位はスレッドの実行順序を保証するものではなく、プラットフォームに大きく依存します。


スレッドを作成する

Java には、スレッドを作成する 2 つの方法があります:

  • Runable インターフェースを実装することによる方法;

  • Thread クラス自体を継承することによる方法。


Runnable インターフェースを実装してスレッドを作成する

スレッドを作成するには、Runnable インターフェースを実装するクラスを作成するのが最も簡単な方法です。

Runnable を実装するには、クラスは次のように宣言された run() というメソッドを実行するだけで済みます:


public void run()

このメソッドはオーバーライドできます。run() は他のメソッドを呼び出すことができることを理解することが重要です他のクラスを使用し、メインスレッドと同じように変数を宣言します。

Runnable インターフェースを実装するクラスを作成した後、クラス内でスレッド オブジェクトをインスタンス化できます。

Thread はいくつかのコンストラクターを定義します。次のコンストラクターは私たちがよく使用するものです:

Thread(Runnable threadOb,String threadName);

ここで、threadOb は Runnable インターフェイスを実装するクラスのインスタンスであり、threadName は新しいスレッドの名前を指定します。

新しいスレッドが作成された後は、その start() メソッドを呼び出すまで実行されません。

void start();

以下は、スレッドを作成して実行を開始する例です:

// 创建一个新的线程
class NewThread implements Runnable {
   Thread t;
   NewThread() {
      // 创建第二个新线程
      t = new Thread(this, "Demo Thread");
      System.out.println("Child thread: " + t);
      t.start(); // 开始线程
   }
  
   // 第二个线程入口
   public void run() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Child Thread: " + i);
            // 暂停线程
            Thread.sleep(50);
         }
     } catch (InterruptedException e) {
         System.out.println("Child interrupted.");
     }
     System.out.println("Exiting child thread.");
   }
}
 
public class ThreadDemo {
   public static void main(String args[]) {
      new NewThread(); // 创建一个新线程
      try {
         for(int i = 5; i > 0; i--) {
           System.out.println("Main Thread: " + i);
           Thread.sleep(100);
         }
      } catch (InterruptedException e) {
         System.out.println("Main thread interrupted.");
      }
      System.out.println("Main thread exiting.");
   }
}

上記のプログラムをコンパイルし、結果を次のように実行します:

Child thread: Thread[Demo Thread,5,main]
Main Thread: 5
Child Thread: 5
Child Thread: 4
Main Thread: 4
Child Thread: 3
Child Thread: 2
Main Thread: 3
Child Thread: 1
Exiting child thread.
Main Thread: 2
Main Thread: 1
Main thread exiting.

Thread を継承してスレッドを作成する

2 番目の方法スレッドを作成するには、Thread クラスを継承する新しいクラスを作成し、そのクラスのインスタンスを作成します。

継承されたクラスは、新しいスレッドのエントリ ポイントである run() メソッドをオーバーライドする必要があります。実行するには start() メソッドも呼び出す必要があります。

// 通过继承 Thread 创建线程
class NewThread extends Thread {
   NewThread() {
      // 创建第二个新线程
      super("Demo Thread");
      System.out.println("Child thread: " + this);
      start(); // 开始线程
   }
 
   // 第二个线程入口
   public void run() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Child Thread: " + i);
                            // 让线程休眠一会
            Thread.sleep(50);
         }
      } catch (InterruptedException e) {
         System.out.println("Child interrupted.");
      }
      System.out.println("Exiting child thread.");
   }
}
 
public class ExtendThread {
   public static void main(String args[]) {
      new NewThread(); // 创建一个新线程
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Main Thread: " + i);
            Thread.sleep(100);
         }
      } catch (InterruptedException e) {
         System.out.println("Main thread interrupted.");
      }
      System.out.println("Main thread exiting.");
   }
}

上記のプログラムをコンパイルした結果は次のとおりです:

Child thread: Thread[Demo Thread,5,main]
Main Thread: 5
Child Thread: 5
Child Thread: 4
Main Thread: 4
Child Thread: 3
Child Thread: 2
Main Thread: 3
Child Thread: 1
Exiting child thread.
Main Thread: 2
Main Thread: 1
Main thread exiting.

Threadメソッド

次の表は、Threadクラスのいくつかの重要なメソッドをリストしています:

シリアル番号方法の説明
1public void start()
スレッドの実行を開始します。 Java 仮想マシンはスレッドの run メソッドを呼び出します。
2パブリックボイドラン()
スレッドが別の Runnable 実行オブジェクトを使用して構築された場合、Runnable オブジェクトの run メソッドが呼び出されます。それ以外の場合、メソッドは何もせずに戻ります。
3public Final void setName(文字列名)
スレッド名をパラメータ名と同じになるように変更します。
4public Final void setPriority(int priority)
スレッドの優先順位を変更します。
5public Final void setDaemon(boolean on)
スレッドをデーモン スレッドまたはユーザー スレッドとしてマークします。
6パブリック最終ボイド結合(長いミリ秒)
このスレッドが終了するまでの最大待機時間はミリ秒です。
7public void中断()
スレッドを中断します。
8パブリック最終ブール値 isAlive()
スレッドがアクティブかどうかをテストします。

スレッドがアクティブかどうかをテストします。 上記のメソッドは Thread オブジェクトによって呼び出されます。以下のメソッドはThreadクラスの静的メソッドです。

シリアル番号方法の説明
1public static void yield()
現在実行中のスレッド オブジェクトを一時停止し、他のスレッドを実行します。
2パブリック静的ボイドスリープ(長いミリ秒)
システム タイマーとスケジューラの精度に応じて、現在実行中のスレッドを指定されたミリ秒間スリープ (実行を一時停止) させます。
3public static booleanholdsLock(Object x)
現在のスレッドが指定されたオブジェクトのモニター ロックを保持している場合に限り、true を返します。
4public static Thread currentThread()
現在実行中のスレッド オブジェクトへの参照を返します。
5public 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 counter = 0;
      int guess = 0;
      do
      {
          guess = (int) (Math.random() * 100 + 1);
          System.out.println(this.getName()
                       + " guesses " + guess);
          counter++;
      }while(guess != number);
      System.out.println("** Correct! " + this.getName()
                       + " in " + counter + " 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...");
   }
}

実行結果は次のとおりで、実行ごとの結果は異なります。

Starting hello thread...
Starting goodbye thread...
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Thread-2 guesses 27
Hello
** Correct! Thread-2 in 102 guesses.**
Hello
Starting thread4...
Hello
Hello
..........remaining result produced.

スレッドのいくつかの主要な概念:

マルチスレッド プログラミングでは、次の概念を理解する必要があります:

  • スレッド同期

  • スレッド間通信

  • スレッド

    スレッド制御: サスペンド、停止、再開
  • マルチスレッドの使用

マルチスレッドを効果的に利用するための鍵は、プログラムが逐次ではなく同時に実行されることを理解することです。例: プログラム内に同時に実行する必要があるサブシステムが 2 つあります。この場合、マルチスレッド プログラミングを使用する必要があります。

マルチスレッドを使用すると、非常に効率的なプログラムを作成できます。ただし、スレッドを作成しすぎると、プログラムの実行効率が向上するのではなく、実際には低下することに注意してください。

コンテキスト切り替えのオーバーヘッドも重要であることに注意してください。作成するスレッドが多すぎると、CPU はプログラムの実行よりもコンテキストの切り替えに多くの時間を費やすことになります。