[Java] 23. スレッドプール(Threadpool)を使う方法
こんにちは。明月です。
この投稿はJavaでスレッドプール(Threadpool)を使う方法に関する説明です。
以前の投稿でJavaでスレッドを使う方法に関する説明したことがあります。
link - [Java] 22.スレッド(Thread)を使う方法
スレッドは別に制限がなく、生成するたびに生成されます。例えばfor文で1から100まで繰り返しを作ってスレッドを生成すればスレッドは100個まで生成されます。
適切なスレッド個数で並列処理するとかなり早い演算処理をしますが、我々のハードウェアは物理的な容量の限界があるので、無限にスレッドを生成することでプログラムのパフォーマンスが速くなることではないです。つまり容量の限界が届くとスレッドの管理するリソースのせいでメモリやシステムリソースがいっぱいになってシングルスレッド(main threadだけ)より遅くなる結果になります。
Javaではプログラムではなく、システムによってスレッド個数を管理して運用するライブラリがあり、それがスレッドプール(Threadpool)ということです。
スレッドプール(Threadpool)とはプール(pool)の中でスレッドを生成してそのプールのなかでスレッドの個数やリソース、運用メモリなどを管理することです。
または、スレッド個数を管理することもありますが、スレッド再使用率を管理して全般的なパフォーマンスも管理します。プログラム中でスレッドを生成、消滅することでかなり時間がかかります。実はスレッドだけではなく、リソース系オブジェクトは生成や消滅が時間がかかりますね。
でも、スレッドプールではスレッドを先に生成してスレッドを呼ばれたら生成されたオブジェクトに載せて使うことでスレッド生成、消滅時間が守ります。つまり、ただのスレッドを使うことよりスレッドプールを利用してスレッド再使用率は上げることで全般的にシステムパフォーマンスが改善することができます。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Example {
  // スレッドsleep関数(sleep関数のException取り除く用)
  private static void sleep() {
    try {
      // スレッド1秒待機
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
  // 実行関数
  public static void main(String[] main) {
    // singleスレッドプール -> スレッドプール中でスレッドが一つだけ動く。
    ExecutorService service = Executors.newSingleThreadExecutor();
    // スレッドプールにスレッドを実行する。
    service.execute(() -> {
      // 0から9まで繰り返し
      for (int i = 0; i < 10; i++) {
        // コンソール出力
        System.out.println(Thread.currentThread().getName() + "  " + i);
        // 1秒待機
        sleep();
      }
    });
    // スレッドプールにスレッドを実行する。
    service.execute(() -> {
      // 0から9まで繰り返し
      for (int i = 0; i < 10; i++) {
        // コンソール出力
        System.out.println(Thread.currentThread().getName() + "  " + i);
        // 1秒待機
        sleep();
      }
    });
    // スレッドプールにスレッドを実行する。
    service.execute(() -> {
      // 0から9まで繰り返し
      for (int i = 0; i < 10; i++) {
        // コンソール出力
        System.out.println(Thread.currentThread().getName() + "  " + i);
        // 1秒待機
        sleep();
      }
    });
    // スレッドプール中のスレッドがすべて正常終了ならスレッドプールを終了。
    service.shutdown();
  }
}
 
  
上はスレッドプールの中で一つのスレッドだけあるスレッドプールです。結果をみればスレッドを何回に呼ばれても順番でスレッドが実行されます。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Example {
  // スレッドsleep関数(sleep関数のException取り除く用)
  private static void sleep() {
    try {
      // スレッド1秒待機
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
  // 実行関数
  public static void main(String[] main) {
    // スレッドの個数制限がないスレッドプール
    ExecutorService service = Executors.newCachedThreadPool();
    // スレッドプールにスレッドを実行する。
    service.execute(() -> {
      // 0から9まで繰り返し
      for (int i = 0; i < 10; i++) {
        // コンソール出力
        System.out.println(Thread.currentThread().getName() + "  " + i);
        // 1秒待機
        sleep();
      }
    });
    // スレッドプールにスレッドを実行する。
    service.execute(() -> {
      // 0から9まで繰り返し
      for (int i = 0; i < 10; i++) {
        // コンソール出力
        System.out.println(Thread.currentThread().getName() + "  " + i);
        // 1秒待機
        sleep();
      }
    });
    // スレッドプールにスレッドを実行する。
    service.execute(() -> {
      // 0から9まで繰り返し
      for (int i = 0; i < 10; i++) {
        // コンソール出力
        System.out.println(Thread.currentThread().getName() + "  " + i);
        // 1秒待機
        sleep();
      }
    });
    // スレッドプール中のスレッドがすべて正常終了ならスレッドプールを終了。
    service.shutdown();
  }
}
 
  
上のスレッドはスレッド個数の制限がないスレッドプールです。スレッド個数の制限がないので、別にプールを使わなく、ただのnew Threadでインスタンス生成して使うことと同じ感じなスレッドプールです。ただ、スレッドプールの中でスレッドを実行するので、リソース管理ができることだけかな。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Example {
  // スレッドsleep関数(sleep関数のException取り除く用)
  private static void sleep() {
    try {
      // スレッド1秒待機
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
  // 実行関数
  public static void main(String[] main) {
    // スレッドを二つだけ生成したスレッドプール
    ExecutorService service = Executors.newFixedThreadPool(2);
    // スレッドプールにスレッドを実行する。
    service.execute(() -> {
      // 0から9まで繰り返し
      for (int i = 0; i < 10; i++) {
        // コンソール出力
        System.out.println(Thread.currentThread().getName() + "  " + i);
        // 1秒待機
        sleep();
      }
    });
    // スレッドプールにスレッドを実行する。
    service.execute(() -> {
      // 0から9まで繰り返し
      for (int i = 0; i < 10; i++) {
        // コンソール出力
        System.out.println(Thread.currentThread().getName() + "  " + i);
        // 1秒待機
        sleep();
      }
    });
    // スレッドプールにスレッドを実行する。
    service.execute(() -> {
      // 0から9まで繰り返し
      for (int i = 0; i < 10; i++) {
        // コンソール出力
        System.out.println(Thread.currentThread().getName() + "  " + i);
        // 1秒待機
        sleep();
      }
    });
    // スレッドプール中のスレッドがすべて正常終了ならスレッドプールを終了。
    service.shutdown();
  }
}
 
  
スレッド二つだけに使うスレッドプールを生成したので、結果をみれば始めにスレッドを2つを使います。後、スレッドの処理が終われば続けてスレッドが実行されることを確認できます。
その以外にnewScheduledThreadPool、newWorkStealingPoolがありますが、よく使わないし、私も使ったことがないので他のプールと差異はよく分かりませんね。
上の例のThreadProolではexecute関数を使います。execute関数にはパラメータがインタフェースRunnableを使うのでラムダ式で作成することができます。
でもスレッドプールの中では様々なスレッドが並列で実行されているので、スレッドのすべての結果を得るためにはスレッド同期化が必要です。Javaではsumbit関数を使ってリターンの値を得られます。

Callableインタフェースはジェネリックタイプでリターンタイプを決め、リターン値を計算できます。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Example {
  // スレッドsleep関数(sleep関数のException取り除く用)
  private static void sleep() {
    try {
      // スレッド1秒待機
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
  // 実行関数
  public static void main(String[] main) {
    // スレッドを二つだけ生成したスレッドプール
    ExecutorService service = Executors.newFixedThreadPool(2);
    // スレッドプールにスレッドを実行する。
    Future<Integer> data1 = service.submit(() -> {
      // 合算結果
      int sum = 0;
      // 0から9まで繰り返し
      for (int i = 0; i < 10; i++) {
        // コンソール出力
        System.out.println(Thread.currentThread().getName() + "  " + i);
        // 合算
        sum += i;
        // スレッド1秒待機
        sleep();
      }
      // 結果リターン
      return sum;
    });
    // スレッドプールにスレッドを実行する。
    Future<Integer> data2 = service.submit(() -> {
      // 合算結果
      int sum = 0;
      // 10から19まで繰り返し
      for (int i = 10; i < 20; i++) {
        // コンソール出力
        System.out.println(Thread.currentThread().getName() + "  " + i);
        / 合算
        sum += i;
        // スレッド1秒待機
        sleep();
      }
      // 結果リターン
      return sum;
    });
    // スレッドプールにスレッドを実行する。
    Future<Integer> data3 = service.submit(() -> {
      // 合算結果
      int sum = 0;
      // 20から29まで繰り返し
      for (int i = 20; i < 30; i++) {
        // コンソール出力
        System.out.println(Thread.currentThread().getName() + "  " + i);
        / 合算
        sum += i;
        // スレッド1秒待機
        sleep();
      }
      // 結果リターン
      return sum;
    });
    try {
      // 三つ目のスレッド結果をコンソールに出力
      System.out.println(data3.get());
      // dataのスレッドが終わるまで待つ。
      // 二つ目スレッド結果をコンソールに出力
      System.out.println(data2.get());
      // 始めのスレッド結果をコンソールに出力
      System.out.println(data1.get());
    } catch (Throwable e) {
      // エラー発生する時にコンソール出力
      e.printStackTrace();
    }
    // スレッドプール中のスレッドがすべて正常終了ならスレッドプールを終了。
    service.shutdown();
  }
}
 
  
実はexecuteやsubmitのどっちを使っても構いません。executeの関数で変数をクロージャ機能を利用して値を共有してもいいです。でもクロージャ機能がソースの中で多いなら可読性が落ちるので使い方法に合わせて使ったほうがよいです。
改めてまとめます。
| スレッド種類 | 説明 | 
|---|---|
| newSingleThreadExecutor | 一つのスレッドを使うスレッドプール | 
| newCachedThreadPool | 個数の制限がないスレッドプール | 
| newFixedThreadPool | 個数を指定して使うスレッドプール | 
| newScheduledThreadPool | 特定時間を決めて使えるスレッドプール | 
| newWorkStealingPool | 1.8から使えるスレッドプールですが、完全なparallel形(並列処理)で使えるスレッドプールです。 | 
そしてスレッドプールでスレッドを呼び出せる関数が二つがあります。
| 関数名 | 説明 | 
|---|---|
| execute | パラメータはRunnableインタフェースタイプでリターン値は無し。 | 
| submit | パラメータはRunnableインタフェースタイプとCallableインタフェースタイプがオーバーロードされているので、リターンタイプがvoidやジェネリックタイプによるオブジェクトタイプやどっちでも可能。 | 
スレッドプール終了するために関数は三つがあります。
一般的にはshutdown関数でスレッドプール中でスレッドが終了するとスレッドプールを終了する関数です。
shutdownNowの場合はスレッドプールのスレッドの状況は関係しずに、スレッドプールを終了することです。awaitTerminationは時間の間に終了しなければ強制終了する関数です。
| 関数名 | 説明 | 
|---|---|
| shutdown | スレッドプール中でスレッドが終了するとスレッドプールを終了 | 
shutdownNow  |  
      スレッドプールのスレッドの状況は関係しずに、スレッドプールを終了 | 
| awaitTermination | 時間の間に終了しなければ強制終了する関数 | 
ここまでJavaでスレッドプール(Threadpool)を使う方法に関する説明でした。
ご不明なところや間違いところがあればコメントしてください。
- [Java] 30. Reflection機能を使う方法(Method編)2019/09/19 20:20:10
 - [Java] 29. Reflection機能を使う方法(Class編)2019/09/18 20:02:14
 - [Java] 28. 文字タイプ(CharacterSet)とエンディアン(endian)で変換する方法2019/09/17 20:22:02
 - [Java] 27. ネットワーク通信(Socket)をする方法2019/09/16 23:42:46
 - [Java] 26. ファイル(IO)を扱う方法(ファイル作成、ファイル修正、アクセス日付変更とIOをclose(リソース返却)する理由、Closableインタフェース)2019/09/13 20:03:58
 - [Java] 25. Objectクラス(notify、waitの使い方)2019/09/13 00:58:31
 - [Java] 24. Javaの同期化(Synchronized)とデッドロック(Deadlock)2019/09/11 23:06:09
 - [Java] 23. スレッドプール(Threadpool)を使う方法2019/09/10 21:55:36
 - [Java] 22.スレッド(Thread)を使う方法2019/09/06 22:30:49
 - [Java] 21. アノテーション(Annotation)を使う方法2019/09/05 22:58:20
 - [Java] 20. iterator(for-each)とStream APIを使う方法2019/09/04 20:11:28
 - [Java] 19. ラムダ(Lambda)を使う方法2019/09/03 20:37:14
 - [Java] 18. 匿名クラス(Anonymous class)とクロージャ(closure)2019/09/02 20:30:34
 - [Java] 17. ジェネリックタイプ(Generic type)を使う方法2019/08/27 19:05:44
 - [Java] 16. 例外処理(try~catch~finally, throw)を使う方法2019/08/26 23:40:29
 
- check2024/04/10 19:03:53
 - [Java] 64.Spring bootとReactを連結する方法(Buildする方法)2022/03/25 21:02:18
 - [Javascript] Node.jsをインストールしてReactを使う方法2022/03/23 18:01:34
 - [Java] 63. Spring bootでcronスケジューラとComponentアノテーション2022/03/16 18:57:30
 - [Java] 62. Spring bootでWeb-Filterを設定する方法(Spring Security)2022/03/15 22:16:37
 - [Java] JWT(Json Web Token)を発行、確認する方法2022/03/14 19:12:58
 - [Java] 61. Spring bootでRedisデータベースを利用してセッションクラスタリング設定する方法2022/03/01 18:20:52
 - [Java] 60. Spring bootでApacheの連結とロードバランシングを設定する方法2022/02/28 18:45:48
 - [Java] 59. Spring bootのJPAでEntityManagerを使い方2022/02/25 18:27:48
 - [Java] 58. EclipseでSpring bootのJPAを設定する方法2022/02/23 18:11:10
 - [Java] 57. EclipseでSpring bootを設定する方法2022/02/22 19:04:49
 - [Python] Redisデータベースに接続して使い方2022/02/21 18:23:49
 - [Java] Redisデータベースを接続して使い方(Jedisライブラリ)2022/02/16 18:13:17
 - [C#] Redisのデータベースを接続して使い方2022/02/15 18:46:09
 - [CentOS] Redisデータベースをインストールする方法とコマンドを使い方2022/02/14 18:33:07