본문 바로가기

Java

[Java] Executor를 이용한 병렬 처리

자바 내부에서 여러 쓰레드를 생성하여 병렬로 수행해야할 때가 있다.
이 경우 Runnable이나 Thread를 직접 생성하는 경우가 있는데 이럴 필요 없이 java.util.concurrent의 패키지에 있는 코어 기능을 사용하는게 더 편하다.
JDK가 1.5이상이면 사용할 수 있다. 정말 오래된 프로젝트가 아닌 이상.. 있더라도 제네릭을 쓰기위해선 1.5라도 올리는게 좋을 것 같다

Executors, ExecutorService : ThreadPool이라고 생각하면 된다.
Callable : Runnable과 유사하지만 Runnable은 return값이 void이고 Callable은 작업할 내용과 리턴 값을 지정할 수 있다.
Future : 비동기로 수행한 쓰레드가 수행한 결과를 담는다.

전체 샘플 코드

@Test
public void testParallelExecute() throws Exception {
	List<String> testSample = Arrays.asList("가", "나", "다", "라", "마", "바", "사", "후후", "1", "10", "100", "-100");
	
	// 4개의 Thread를 가진 ThreadPool생성
	ExecutorService threadPool = Executors.newFixedThreadPool(4);
	
	// Thread들이 비동기로 수행되면 그 결과를 담을 Futrure 객체
	List<Future<Video>> futures = new ArrayList<Future<Video>>();
	for (final String sample : testSample) {
		// callable 객체를 통해 어떤 일을 수행할지 결정한다.
		Callable<Video> callable = new Callable<Video>() {
			@Override
			public Video call() throws Exception {
				System.out.println("Time : "+ new Date() + " -Thread Name : " + Thread.currentThread().getName() + " - Text : " + sample);
				
				// 테스트겸 3번만 0.5초간 중지
				if (Thread.currentThread().getName().equals("pool-1-thread-3")) {
					Thread.sleep(50);
				}
				
				Video video = new Video();
				video.setDialog(sample);
				return video;
			}
		};
		
		// 생성된 callable들을 threadpool에서 수행시키고 결과는 Future 목록에 담는다.
		futures.add(threadPool.submit(callable));
	}
	
	// 수행중인 callable들이 다 끝나면 threadpool을 종료시킨다.(반드시 해야함)
	// 자동으로 제거되지 않는다.
	// showdownNow()는 수행중인 callable이 있더라도 인터럽트시켜 강제 종료한다.
	threadPool.shutdown();
	
	List<Video> results = new ArrayList<Video>(); 
	for (Future<Video> future : futures) {
		// future에 담긴 결과 객체를 받아 List에 담는다.
		results.add(future.get());
	}
	
	// 수행 소비 시간에 상관없이 futures에 담긴 순서대로 출력된다.
	assertFalse(results.isEmpty());
	for (Video result : results) {
		System.out.println(result.getDialog());
	}
}
- List testSample = Arrays.asList("가", "나", "다", "라", "마", "바", "사", "후후", "1", "10", "100", "-100");
이 샘플들을 Video라는 객체에 데이터를 넣을 예정이다.

- ExecutorService threadPool = Executors.newFixedThreadPool(4);
4개의 쓰레드를 관리하는 Pool이다.

- List<Future<Video>> futures = new ArrayList<Future<Video>>();
비동기로 수행된 쓰레드의 결과를 담을 목록

- Callable<Video> callable = new Callable<Video>()
반환되는 값은 Video객체를 명시한다.
call 메서드를 통해 처리할 작업을 지정한다. (JDK8 이상이면 익명 클래스가 아닌 람다로 간편히 해결할 수 있다)

- futures.add(threadPool.submit(callable));
submit을 통해 call 메서드를 수행시키고 결과는 Future 목록에 담는다.

- threadPool.shutdown();
작업들이 모두 끝나면 ThreadPool을 종료한다. 자동으로 사라지지 않는다!

for (Future<Video> future : futures) {
		// future에 담긴 결과 객체를 받아 List에 담는다.
		results.add(future.get());
	}
- Future에 담긴 Video 결과를 꺼내 Video 목록에 담는다.
get은 앞선 작업들이 완료되면 가져 온다. 그 동안 기다린다.