English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Java 基础教程

Java フローコントロール

Java 配列

Java 面向オブジェクト(I)

Java 面向オブジェクト(II)

Java 面向オブジェクト(III)

Java 異常処理

Java 列表(List)

Java Queue(キュー)

Java Map集合

Java Set集合

Java 入出力(I/O)

Java Reader/Writer

Javaの他のトピック

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

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インターフェースを実装したクラスを作成することです。

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を継承してスレッドを作成する

スレッドを生成する第二种方法は、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 メソッド

以下に Thread クラスの重要なメソッドの一覧を示します:

番号メソッドの説明
                1public void start()
このスレッドの実行を開始します。;Java 仮想機がこのスレッドの run メソッドを呼び出します。
                2public void run()
このスレッドが独立した Runnable 実行オブジェクトで構築されている場合、その Runnable オブジェクトの run メソッドを呼び出します;そうでない場合、このメソッドは何も行わずに返します。
                3public final void setName(String name)
スレッドの名前を変更し、それが引数 name と同じになります。
                4public final void setPriority(int priority)
 スレッドの優先度を変更します。
                5public final void setDaemon(boolean on)
このスレッドをデフォルトスレッドまたはユーザースレッドにマークします。
                6public final void join(long millisec)
このスレッドが終了するまで待機する時間は最長millisミリ秒です。
                7public void interrupt()
スレッドを中断します。
                8public final boolean isAlive()
スレッドがアクティブであるかどうかをテストします。

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

番号メソッドの説明
                1public static void yield()
現在実行中のスレッドオブジェクトを一時停止し、他のスレッドを実行します。
                2public static void sleep(long millisec)
指定されたミリ秒数内に現在実行中のスレッドをスリープ(一時停止)します。この操作はシステムのカウンタとスケジューラの精度と正確性に影響されます。
                3public static boolean holdsLock(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 ファイルコード:

// ファイル名 : 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 ファイルコード:

// ファイル名 : 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...");
   }
}

実行結果は以下の通り、各回の実行結果は異なります。

こんにちはスレッドの起動...
さようならスレッドの起動...
こんにちは
こんにちは
こんにちは
こんにちは
こんにちは
こんにちは
さようなら
さようなら
さようなら
さようなら
さようなら
.......

Callable と Future を使用してスレッドを作成

  • 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;  
    }  
}

スレッドの作成方法の3つの比較

  • 1. Runnable、Callable インターフェースを実装してマルチスレッドを作成する方法では、スレッドクラスは Runnable インターフェースまたは Callable インターフェースを実装しており、他のクラスを継承することもできます。

  • 2. Threadクラスを継承してマルチスレッドを作成する方法はシンプルで、現在のスレッドにアクセスする場合は Thread.currentThread() メソッドを使用する必要はありません。直接 this を使用して現在のスレッドを取得できます。

スレッドの主要な概念

マルチスレッドプログラミングを行う際には、以下の概念を理解する必要があります:

  • スレッド同期

  • スレッド間通信

  • スレッド死锁

  • スレッド制御:一時停止、停止と復旧

マルチスレッドの使用

マルチスレッドの効果的な利用の鍵は、プログラムが並行実行されていることを理解することです。例えば、プログラムに2つの並行実行が必要なサブシステムがある場合、マルチスレッドプログラミングを利用する必要があります。

マルチスレッドの使用により、非常に効率的なプログラムを書くことができます。ただし、多くのスレッドを作成すると、プログラムの実行効率は低下しますが、向上するわけではありません。

また、コンテキストの切り替えのコストも非常に重要です。もし多くのスレッドを作成すると、CPUがコンテキストの切り替えに費やす時間がプログラムの実行時間を超えることになります!