Java ExecutorServiceでマルチスレッドのサンプル

JavaのインターフェースのExecutorServiceでマルチスレッドを実行するサンプルです。

目次

サンプル ExecutorServiceインターフェース
1つのスレッドを生成(newSingleThreadExecutor)
複数のスレッドを生成(newFixedThreadPool)
スレッドをデーモン化した場合

ExecutorServiceインターフェース

public interface ExecutorService extends Executor

1つのスレッドを生成(newSingleThreadExecutor)

package test1;

import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class Test1 {
	public static void main(final String[] arguments) {
		ExecutorService executor = Executors.newSingleThreadExecutor();
		List<Future<String>> list1 = new ArrayList<>();
		
		for (int i = 0; i < 3; i++) {
			list1.add(executor.submit(new Make1()));
		}
		try {
			for (Future<String> future : list1) {
				future.get();
			}			
		}catch(Exception e) {
			System.out.println(e);
		}

		executor.shutdown();
		System.out.println("ID=" + Thread.currentThread().getId()
				+" "+ LocalTime.now().toString());
		}
}
class Make1 implements Callable<String> {
	@Override
	public String call() throws Exception {
		try {
			TimeUnit.SECONDS.sleep(3);
			System.out.println("ID=" + Thread.currentThread().getId()
					+" "+ LocalTime.now().toString());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return "end";
	}
}

14行目のnewSingleThreadExecutorメソッドは、1つのスレッドを生成します。
15行目は、submitの戻り値を受けるFuture型のリストです。
18行目は、35行目のクラスを実行し戻り値をリストにセットします。
22行目は、getメソッドで戻り値を取得します。これは処理が終わるまで待つという効果があります。これがないとメイン処理が先に終了します。
28行目のshutdownは、新しいタスクを拒否し送信済みのタスクを終了前に実行します。ただし実行完了まで待機しません。
https://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown--

実行結果は以下になります。ID=12のスレッドが3秒ごとに実行され、最後にID=1のスレッドの実行で終了します。

shutdownメソッドを実行しない場合、プロセスが残る場合があるので注意が必要です。

 

クラスを使用しない場合

上記コードのmake1クラスを使用しない+ループしない場合です。

package test1;

import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class Test1 {
	public static void main(final String[] arguments) {
		ExecutorService executor = Executors.newSingleThreadExecutor();
		Future<String> future = executor.submit(() -> {
			try {
				TimeUnit.SECONDS.sleep(1);
				System.out.println("ID=" + Thread.currentThread().getId() + " " + LocalTime.now().toString());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return "end";
		});

		try {
			future.get();
		} catch (Exception e) {
			System.out.println(e);
		}
		executor.shutdown();
		System.out.println("main ID=" + Thread.currentThread().getId() + " " + LocalTime.now().toString());
	}
}

25行目は、別スレッドの終了を待ちます。
30行目は、別スレッドが終了してから出力されます。

複数のスレッドを生成(newFixedThreadPool)

package test1;

import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class Test1 {
	public static void main(final String[] arguments) {
		ExecutorService executor = Executors.newFixedThreadPool(3);
		List<Future<String>> list1 = new ArrayList<>();
		
		for (int i = 0; i < 3; i++) {
			list1.add(executor.submit(new Make1()));
		}
		try {
			for (Future<String> future : list1) {
				future.get();
			}			
		}catch(Exception e) {
			System.out.println(e);
		}

		executor.shutdown();
		System.out.println("ID=" + Thread.currentThread().getId()
				+" "+ LocalTime.now().toString());
		}
}
class Make1 implements Callable<String> {
	@Override
	public String call() throws Exception {
		try {
			TimeUnit.SECONDS.sleep(3);
			System.out.println("ID=" + Thread.currentThread().getId()
					+" "+ LocalTime.now().toString());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return "end";
	}
}

上記newSingleThreadExecutorのサンプルとの違いは14行目のnewFixedThreadPoolで引数を3にしています。スレッドが3つになります。
実行結果は以下になります。ID=12,13,14がそれぞれ実行され、最後にID=1のスレッドの実行で終了します。

クラスを使用しない場合

上記コードのmake1クラスを使用しない場合です。

package test1;

import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class Test1 {
	public static void main(final String[] arguments) {
		ExecutorService executor = Executors.newFixedThreadPool(3);
		List<Future<String>> list1 = new ArrayList<Future<String>>();
		
		for (int i = 0; i < 3; i++) {
			list1.add(executor.submit(()->{
				try {
					TimeUnit.SECONDS.sleep(3);
					System.out.println("ID=" + Thread.currentThread().getId()
						+" "+ LocalTime.now().toString());
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				return "end";					
			}));
		}
		
		try {
			for (Future<String> future : list1) {
				future.get();
			}			
		}catch(Exception e) {
			System.out.println(e);
		}

		executor.shutdown();
		System.out.println("mainID=" + Thread.currentThread().getId()
				+" "+ LocalTime.now().toString());
		}
}

 

スレッドをデーモン化した場合

スレッドをデーモン化した場合で処理を終了すると、スレッドはスレッドの終了を待たずに終了します。(ユーザースレッドは終了を待ちます)

package test1;

import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

public class Test1 {
	public static void main(final String[] arguments) {
		ThreadFactory daemonT = new ThreadFactory() {
			@Override
			public Thread newThread(Runnable r) {
				Thread thread = new Thread(r);
				thread.setDaemon(true);
				return thread;
			}
		};
		ExecutorService executor = Executors.newFixedThreadPool(3, daemonT);	
//		ExecutorService executor = Executors.newFixedThreadPool(3);
		List<Future<String>> list1 = new ArrayList<Future<String>>();

		for (int i = 0; i < 3; i++) {
			list1.add(executor.submit(new Make1()));
		}
//		try {
//			for (Future<String> future : list1) {
//				if (future.get() != "end") {
//					throw new RuntimeException();
//				}
//			}
//		} catch (Exception e) {
//			System.out.println(e);
//		}
		executor.shutdown();
		System.out.println("ID=" + Thread.currentThread().getId() + " " + LocalTime.now().toString());
	}
}
class Make1 implements Callable<String> {
	@Override
	public String call() throws Exception {
		try {
			TimeUnit.SECONDS.sleep(3);
			System.out.println("ID=" + Thread.currentThread().getId() + " " + LocalTime.now().toString());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return "end";
	}
}

15行目は、ThreadFactoryのインスタンスを生成しています。
19行目は、setDaemonメソッドでデーモン化しています。
30~38行目は、デーモン化の確認のためコメントにしています。

以下は、デーモン化した場合です。(23行目のコメント外して24行目をコメント)
スレッドで出力される文字列がありません。

 

以下は、デーモン化していない場合です。(23行目をコメントにして24行目のコメントを外す)
スレッドで出力される文字列があります。

 

デーモン化の状態でタスクの戻り値を待つようにした場合は以下になります。
(30~38行目のコメントを外す)
各スレッドの終了を待ちその後にmain処理が終了します。

関連の記事

Java ラムダ式で関数型インターフェースを使用
Java Stream APIでリストを操作する(stream)

△上に戻る