스레드를 관리하고 실행하기 위한 인터페이스이다.
스레드풀을 생성하고 관리하는 기능을 제공하며, 비동기 작업을 실행하고 완료된 작업의 결과를 리턴 받을 수 있도록 도와준다.
public class ExcutorServiceRunner {
public static void main(String[] args) {
// 싱글스레드 제어자
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Task1());
executorService.execute(new Thread(new Task2()));
// task3
System.out.print("task3 kicked off \n");
for (int i = 301; i <= 399; i++) {
System.out.print(i +" ");
}
System.out.println("\n task3 done");
System.out.println("main done");
executorService.shutdown();
}
}
Executors.newSingleThreadExecutor();
메서드 이름처럼 싱글스레드 제어자를 생성한다.

결과를 확인해보면 task3와 task1,2은 서로 병렬로 처리되고,
task1과 task2는 싱글스레드로 순서대로 처리되는 것을 확인할 수 있다.
executorService.execute(new Task1());
executorService.execute(new Thread(new Task2()));
스레드 객체를 직접 인자로 전달하거나 스레드로 감싸서 전달하는 방법이 있다.
두 방법 다 스레드풀에서 관리하기 때문에 간단하게 첫번째 방법을 쓰는게 편하다.
executorService.shutdown();
스레드를 종료하는 메서드
이 shutdown() 메서드를 호출해주지 않으면 백그라운드에서 스레드가 실행되기 때문에 작업이 끝난 후에는 꼭 호출해주자!!!
class Task extends Thread {
private int number;
public Task(int number) {
this.number = number;
}
@Override
public void run() { // 이부분은 이대로 사용해줘야함
System.out.print("\nTask " + number + " Start");
for (int i = number*100; i <= number*100+99; i++) {
System.out.print(i +" ");
}
System.out.print("\nTask" + number + " done");
}
}
public class ExcutorServiceRunner {
public static void main(String[] args) {
// 멀티스레드 제어자
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.execute(new Task(1));
executorService.execute(new Task(2));
executorService.execute(new Task(3));
executorService.execute(new Task(4));
executorService.execute(new Task(5));
executorService.execute(new Task(6));
executorService.execute(new Task(7));
executorService.execute(new Task(8));
executorService.shutdown();
}
}
결과를 확인해보면

각 스레드가 병렬로 처리가 되고, 특정 시점에 실행중인 스레드의 최대 수는 5개인 것을 확인할 수 있다!!
class CallableTask implements Callable<String> {
private String name;
public CallableTask(String name) {
this.name = name;
}
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "Hello" + name;
}
}
public class CallableRunner {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
Future<String> welcomeFuture = executorService.submit(new CallableTask("qkrmekem"));
System.out.println("new CallableTask executed");
String welcomeMessage = welcomeFuture.get();
System.out.println(welcomeMessage);
System.out.println("\n Main completed");
executorService.shutdown();
}
}

public class MultipleCallableRunner {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
List<CallableTask> callableTasks = List.of(new CallableTask("qkrmekem"),
new CallableTask("test"), new CallableTask("admin"));
List<Future<String>> futures = executorService.invokeAll(callableTasks);
for (Future<String> result : futures) {
System.out.println(result.get());
}
executorService.shutdown();
}
}

public class MultipleAnyCallableRunner {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<CallableTask> callableTasks = List.of(new CallableTask("qkrmekem"),
new CallableTask("test"), new CallableTask("admin"));
String futures = executorService.invokeAny(callableTasks);
for (Future<String> result : futures) {
System.out.println(result.get());
}
executorService.shutdown();
}
}




이처럼 실행할 때마다 반환값이 달라지는 것을 확인할 수 있다.
이것으로 자바에서 스레드를 생성하고 다루는 방법에 대해서 알아보았다. 스프링을 공부하면서 스프링이 주는 편리함과 각 기술이 어떻게 구현되었는지에 대해서 알아보았지만 그보다 더 바탕이되는 자바를 직접 공부해야 하지 않나 하는 생각을 했는데, 막상 이렇게 자바 기술 자체를 알아보고 멀티스레드를 직접 다뤄보니 꽤 재밌는 시간이었다ㅎㅎ