JavaのExecutorServiceを使ってスレッド数を制限する

# 問題
複数のスレッドが平行してDBにアクセスするJavaプログラムを作成した。
ところが、スレッドが多くなるとDBMSで設定したDBコネクションの上限値を超えてしまい、プログラムがフリーズしてしまうことが分かった。

# 解決策
ExecutorServiceを使って、最大スレッド数を制限する。

## ExecutorServiceとは何か
ExecutorServiceはJava標準の平行処理ライブラリ「java.util.concurrent」パッケージに含まれるインターフェースで、生のままでは使いにくいJavaのスレッドをラップし、使いやすくしてくれる。

### ■ ExecutorService
- 標準API
- Java1.5以上
- java.util.concurrentパッケージ

詳しくは[Java8 API仕様 ExecutorService](https://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/ExecutorService.html)を参照。

# テストプログラムの作成
## スレッド
まずはただのスレッドを書く。
実際の処理は何もしないが、処理に時間がかかっているように見せかけるため、引数で与えた時間分、スリープしてから終了するようにする。

```java:SampleThread.java
package sample;

import java.text.SimpleDateFormat;
import java.util.Calendar;

public class SampleThread implements Runnable {
	
	private int no;
	private int time;
	
    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
	
	SampleThread(int no, int time) {
		this.no = no;
		this.time = time;
	}

	@Override
	public void run() {
		System.out.println(" No." + no + " start  id:" + Thread.currentThread().getId() + "  生存時間:" + time + " 現在時刻:" + sdf.format(Calendar.getInstance().getTime()));
		try {
			// 何かの処理が行われていると見せかけるため、スリープさせる。
			Thread.sleep(time * 1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(" No." + no + " end    id:" + Thread.currentThread().getId() + "  生存時間:" + time + " 現在時刻:" + sdf.format(Calendar.getInstance().getTime()));
	}
}
```

## スレッドプールを持つExecutorService
次にExecutorServiceを使って、先ほど書いたスレッドを使ってみる。
ExecutorServiceはインターフェースなので、このインターフェースの実装が必要になるが、ここでExecutors#newFixedThreadPoolメソッドを使って、スレッドプールを持つExecutorServiceを手にいれる。
スレッドプールの上限値は、分かりやすさのために3スレッドとしておく。

```java:Main.java
package sample;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {

	public static void main(String[] args) {
		
		// スレッドプールの最大値を3スレッドに設定。
		final int MAX_THREADS = 3;
		
		// ファクトリメソッド「newFixedThreadPool」を使って、スレッドプールを持つExecutorServiceを生成。
		ExecutorService executor = Executors.newFixedThreadPool(MAX_THREADS);

		// スレッドを10本立ててみる
		for (int i = 0; i < 10; i++) {
			
			// スレッドに番号を振る。
			int no = i;
			
			// スレッドの生存時間を1〜10秒でランダムに決める。
			int lifeTime = (int)(Math.random() * 9 + 1);
			
			// スレッドを起動する。
			executor.submit(new SampleThread(no, lifeTime));
		}
		// ExecutorServiceを閉じる。
		System.out.println("executor.shutdown();");
		executor.shutdown();
	}
}
```

# テストの実施

上記のテストプログラムを流すと、以下がコンソール出力される。

| 項目       　　　　| 意味　　      　　 |
|:-----------------|:-----------------|
| No.             |スレッドを起動するときに引数で与えた処理番号                |
| id              |スレッド固有のid。                |
| 生存時間         |スレッドがスリープしている時間。（秒）                |

```text:コンソール
 No.0 start  id:12  生存時間:1 現在時刻:13:29:41
 No.1 start  id:13  生存時間:8 現在時刻:13:29:41
 No.2 start  id:14  生存時間:6 現在時刻:13:29:41
executor.shutdown();
 No.0 end    id:12  生存時間:1 現在時刻:13:29:42
 No.3 start  id:12  生存時間:1 現在時刻:13:29:42
 No.3 end    id:12  生存時間:1 現在時刻:13:29:43
 No.4 start  id:12  生存時間:9 現在時刻:13:29:43
 No.2 end    id:14  生存時間:6 現在時刻:13:29:47
 No.5 start  id:14  生存時間:2 現在時刻:13:29:47
 No.1 end    id:13  生存時間:8 現在時刻:13:29:49
 No.6 start  id:13  生存時間:9 現在時刻:13:29:49
 No.5 end    id:14  生存時間:2 現在時刻:13:29:49
 No.7 start  id:14  生存時間:7 現在時刻:13:29:49
 No.4 end    id:12  生存時間:9 現在時刻:13:29:52
 No.8 start  id:12  生存時間:7 現在時刻:13:29:52
 No.7 end    id:14  生存時間:7 現在時刻:13:29:56
 No.9 start  id:14  生存時間:4 現在時刻:13:29:56
 No.6 end    id:13  生存時間:9 現在時刻:13:29:58
 No.8 end    id:12  生存時間:7 現在時刻:13:29:59
 No.9 end    id:14  生存時間:4 現在時刻:13:30:00
```

# テスト結果の分析

## スレッドの最大数制限
「No.」の部分をみると、0〜9までのNo.があり、スレッドが10回処理されている。
一方、スレッドのidを見てみると、12〜14しかなく、スレッドの数は3に抑えられていることが分かる。
また、一つのスレッドの処理が終わると、同じidのスレッドが次の処理で使われていることも確認できる。
これらのことから、3本のスレッドが使い回されていると言える。
また、同時に実行されるスレッドの最大数が3であることも分かる。

## ExecutorServiceの呼び出し元はブロックされない
スレッドの最大数の3本が起動された直後にexecutor#shutdownメソッドが呼ばれている。
これは、スレッドがスレッドプールの最大数に達しているときに、スレッドの起動元のsubmitがブロックされるのではなく、タスクそのものは全部FIFOのQueueで受け付けるためである。
このQueueが正しく動作していることも、併せて確認できる。

# サンプルコード
https://github.com/nogitsune413/ExecutorServiceSample
# 参考
[Java8 API仕様 ExecutorService](https://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/ExecutorService.html)
[ExecutorService の使い方](http://java.keicode.com/lang/multithreading-executor.php)
[Thread.start()実行をやめよう。｢ExecutorService｣でスレッド管理が簡単に。](http://java-study.blog.jp/archives/1036862519.html)
[ExecutorServiceを使って、Javaでマルチスレッド処理](http://qiita.com/mmmm/items/f33b757119fc4dbd6aa1)

# 確認環境
Java 1.8