2
1

More than 1 year has passed since last update.

Java マルチスレッド処理~Exector編~

Last updated at Posted at 2023-06-28

3章Executor編です!
Javaのマルチスレッド処理は本編が最終章としていったん区切りとなります。
1編Runnable、2編Threadと比べ、メリットはありますが、できることが増えた分理解が難しいですが、
慣れれば一番使い勝手が良いと感じました。

特徴

  • Executorフレームワークのメソッドを使用する
  • スレッドプールを使用できる
  • 遅延実行や定期実行する処理を簡単に実装できる

スレッドプールってなに❔

端的に言うと「空のスレッドをいくつか用意しておいて、タスクを実行するスレッドと
タスクが与えられるまで待機するスレッドを分けることで、スレッドの無駄使いを解決する仕組み」です。
1,2章のマルチスレッド処理では、100個の実行したい処理があれば、100個のスレッドを作成することになります。
100個のスレッドを次々に作成して処理を実行している間に最初のころに作成したスレッドは処理を完了して空き状態になっているかもしれません。
それにも関わらず、コンピュータに負荷を与えて新たにスレッドを作成していしまうのです。
スレッドプールを活用すれば、決まったスレッド数で無駄なく処理を実行できるということです。

使用方法

Executorを使用する場合はそれぞれの処理で使用するインスタンスをファクトリメソッド(インスタンスを生成するメソッド)で取得します。
※ファクトリメソッドもいつかしっかり勉強して解説したい。。。

  • 1 newSingleThreadExecutor()
    • シングルスレッドでタスクの処理を行います。
  • 2 newFixedThreadPool(int スレッド数)
    • 指定したスレッド数でタスクの並列処理を行います。
  • 3 newSingleThreadScheduledExecutor()  (←次回解説予定)
    • シングルスレッドで遅延実行や定期的なタスクの処理を行います。

スレッドを実行した結果を返す/返さないはタスクを実行する際のメソッドにより変わります。

  • execute( Runnable )
    • 戻り値なし Runnableタスクを実行して戻り値を返しません。
  • submit( Runnable )
    • 戻り値あり( null ) Runnableタスクを実行して戻り値を返します。
  • submit( Callable )
    • 戻り値あり Callableタスクを実行して戻り値を返します。

executeは「実行する」submitは「送信する」という意味なので送信したら返信があるsubmitが戻り値ありと覚えれそうですね。

実行例1 シングルスレッドでタスクの処理を行う

newSingleThreadExecutorメソッドを使用します

//タスクを実行するクラス(Runnableインターフェースを実装)
class RunnableImpl implements Runnable {
	public void run() {
		for (int i = 0; i < 3; i++) {
			System.out.println("run :" + Thread.currentThread().getName() + ":" + i);
			//実行ごとに1秒間スリープする
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
//タスクの実行要求を行うクラス
class Main {
	public static void main(String[] args) {
		ExecutorService execService = null;
		try {
			//シングルスレッドでタスクを処理するオブジェクトを取得
			execService = Executors.newSingleThreadExecutor();
			//実行
			System.out.println("タスクを3回実行");
			for (int i = 0; i < 3; i++) {
				//戻り値なし(execute)でタスクを実行
				execService.execute(new RunnableImpl());
			}
		} finally {
			//ExectorServiceは明示的に終了する必要がある
			execService.shutdown();
			System.out.println("ExecutorService をシャットダウン");
		}
	}
}

実行結果

実行結果からpool-1-thread-1で3回処理が実行されているのがわかります。
image.png

実行例2(戻り値あり:submitメソッドでタスクを実行)

戻り値を受け取る場合はsubmitメソッドを使用します。

//タスクを実行するクラス(Callableインターフェースを実装)
class CallableImpl implements Callable<String> {
	//submitメソッドが呼び出された際に行うタスク
	public String call() throws Exception {
		for (int i = 0;i<3;i++) {
			System.out.println("run "+ Thread.currentThread().getName() + ":" + i);
			//実行ごとに1秒間スリープする
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//戻り値
		return new Date().toString();
	}
}
//タスクの実行要求を行うクラス
class Main {
	public static void main(String[] args) {
		ExecutorService execService = null;
		try {
			//シングルスレッドでタスクを処理するオブジェクトを取得
			execService = Executors.newSingleThreadExecutor();
			//実行
			System.out.println("タスクを3回実行");
			for (int i = 0; i < 3; i++) {
				//戻り値あり(submit)でタスクを実行
				Future<String> result = execService.submit(new CallableImpl());
				//戻り値を取得するにはFutureのgetメソッドを使う
				try {
					System.out.println(result.get());
				} catch (InterruptedException | ExecutionException e) {
					e.printStackTrace();
				}
			}
		} finally {
			//ExectorServiceは明示的に終了する必要がある
			execService.shutdown();
			System.out.println("ExecutorService をシャットダウン");
		}
	}
}

実行結果

先ほどとは違い、CallableImplから受け取った日付を出力しているのが確認できます。
image.png

実行例1 指定した数のスレッドで並列処理を行う(戻り値なし)

newFixedThreadPool(int スレッド数)で固定数のスレッドで処理が行えます。

//タスクを実行するクラス(Runnableインターフェースを実装)
class RunnableImpl implements Runnable {
	public void run() {
		for (int i = 0; i < 3; i++) {
			System.out.println("run :" + Thread.currentThread().getName() + ":" + i);
			//実行ごとに1秒間スリープする
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
//タスクの実行要求を行うクラス
class Main {
	public static void main(String[] args) {
		ExecutorService execService = null;
		try {
			//固定数のスレッドで処理を行うオブジェクトを取得
			execService = Executors.newFixedThreadPool(2);
			//実行
			System.out.println("タスクを3回実行");
			for (int i = 0; i < 3; i++) {
				//戻り値なし(execute)でタスクを実行
				execService.execute(new RunnableImpl());
				//戻り値を取得するにはFutureのgetメソッドを使う
			}
		} finally {
			//ExectorServiceは明示的に終了する必要がある
			execService.shutdown();
			System.out.println("ExecutorService をシャットダウン");
		}
	}
}

実行結果

赤線より上の2つのタスクは、指定した2つのスレッドが同時に実行しています。
そして赤線より下の残り1タスクはタスクが先に終了したスレッドが使われ実行しているのがわかります。
image.png

以上!!
次回はScheduledExecutorServiceインターフェースのメソッドを使用した「タスクの遅延・定期実行」です。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1