[Java][Thread] 쓰레드의 동기화 #4 fork & join 프레임워크

2022. 7. 5. 22:38JAVA/Language

fork & join 프레임워크

  • JDK 1.7
  • 하나의 작업을 작은 단위로 나눠서 여러 쓰레드가 동시에 처리하는 것을 쉽게 만들어줍니다.

 

1. fork & join 프레임워크 종류

RecursiveAction 반환값이 없는 작업을 구현할 때 사용
RecursiveTask   반환값이 있는 작업을 구현할 때 사용

 

RecursiveAction 추상 클래스 형식

public abstract class RecursiveAction extends ForkJoinTask<Void>{
    ...
    protected abstract void compute(); // 상속을 통해 이 메서드를 구현해야함
    ...
}

 

RecursiveTask 추상 클래스 형식

public abstract class RecursiveTask extends ForkJoinTask<V>{
    ...
    V result;
    protected abstract V compute(); // 상속을 통해 이 메서드를 구현해야함
    ...
}

 

다음 코드는 1부터 n까지의 합을 계산한 결과를 돌려주는 작업입니다.

RecursiveTask 클래스 상속

public class SumTask extends RecursiveTask<Long>{
	long from, to;

	public SumTask(long from, long to) {
		this.from = from;
		this.to = to;
	}
	
	@Override
	protected Long compute() {
        // 처리할 작업을 수행하기 위한 문장을 넣습니다.
	}
}

 

쓰레드 풀 생성 및 수행할 작업 시작

ForkJoinPool pool = new ForkJoinPool(); // 쓰레드 풀 생성
SumTask task = new SumTask(from, to);   // 수행할 작업 생성
Long result = pool.invoke(task);        // invoke()를 호출해서 작업을 시작

 

2. compute 구현

	// 숫자의 합을 반환. sum()은 from부터 to까지의 수를 더해서 반환	
	public Long sum() {
		long sum = 0;
		for(long i = from; i <= to; i++) {
			sum += i;
		}
		return sum;
	}

	@Override
	protected Long compute() {
		long size = to - from + 1; // from <= i <= to
		
		if(size <= 5) { // 더할 숫자가 5개 이하이면
			return sum(); 
		}
		
		// 범위를 반으로 나눠서 두 개의 작업을 생성
		long half = (from + to) / 2;
		
		SumTask leftSum = new SumTask(from, half);
		SumTask rightSum = new SumTask(half+1, to);
		
		leftSum.fork(); // 작업(leftSum)을 작업 큐에 넣음, 비동기 메서드
		
		return rightSum.compute() + leftSum.join(); // join : 동기 메서드
		
	}
  • 작업의 범위를 반으로 나누면서 새로운 작업을 생성해서 실행합니다.
  • 데이터의 개수가 5개 이하라면 실제 작업(sum 메서드)를 수행합니다.

위 구현을 그림으로 표현하면 다음과 같습니다.

3. 다른 쓰레드의 작업 훔쳐오기

  • 자신의 작업 큐가 비어있는 쓰레드는 다른 쓰레드의 작업 큐에서 작업을 가져와서 수행합니다.
  • 작업을 훔쳐오는 과정은 쓰레드 풀에 의해 자동적으로 이루어집니다.

4. fork() & join()

fork() : 해당 작업을 쓰레드 풀의 작업 큐에 넣는다. 비동기 메서드
join() : 해당 작업의 수행이 끝날 때까지 기다렸다가, 수행이 끝나면 그 결과를 반환한다. 동기 메서드
  • 비동기 메서드 : 해당 메서드를 호출하고 실행이 끝날때까지 기다리지 않고 바로 다음 명령어를 수행합니다.
  • 동기 메서드 : 메서드를 호출하고 실행이 끝날때까지 기다립니다.

 

다음은 위 예제의 1부터 n까지의 합을 계산하는 예제의 전체 코드입니다.

public class SumTask extends RecursiveTask<Long>{
	long from, to;

	public SumTask(long from, long to) {
		this.from = from;
		this.to = to;
	}
	
	// 숫자의 합을 반환. sum()은 from부터 to까지의 수를 더해서 반환	
	public Long sum() {
		long sum = 0;
		for(long i = from; i <= to; i++) {
			sum += i;
		}
		return sum;
	}

	@Override
	protected Long compute() {
		long size = to - from + 1; // from <= i <= to
		
		if(size <= 5) { // 더할 숫자가 5개 이하이면
			return sum(); 
		}
		
		// 범위를 반으로 나눠서 두 개의 작업을 생성
		long half = (from + to) / 2;
		
		SumTask leftSum = new SumTask(from, half);
		SumTask rightSum = new SumTask(half+1, to);
		
		leftSum.fork(); // 작업(leftSum)을 작업 큐에 넣음, 비동기 메서드
		
		return rightSum.compute() + leftSum.join(); // join : 동기 메서드
		
	}
}

public class SumTaskTest {
	static final ForkJoinPool pool = new ForkJoinPool();     // 쓰레드 풒생성
	@Order(1)
	@Test
	void recursiveTaskTest() {
		long from = 1L, to = 100_000_000L;
		SumTask task = new SumTask(from, to);     // 수행할 작업을 생성
		
		long start = System.currentTimeMillis();
		Long result = pool.invoke(task);
		long end = System.currentTimeMillis();
		System.out.println("Elapsed time(4Core) : " + (end - start));
		System.out.printf("sum of %d~%d=%d\n", from, to, result);
		System.out.println();

		result = 0L;
		start = System.currentTimeMillis();
		for(long i = from; i <= to; i++) {
			result += i;
		}
		end = System.currentTimeMillis();
		
		System.out.println("Elapsed time(1Core) : " + (end - start));
		System.out.printf("sum of %d~%d=%d\n", from, to, result);
	}
}
Elapsed time(4Core) : 1081
sum of 1~100000000=5000000050000000

Elapsed time(1Core) : 470
sum of 1~100000000=5000000050000000

실행결과 fork & join 프레임워크로 계산한 결과보다 for문으로 계산한 결과가 시간이 덜 거린것을 알 수 있습니다. 왜나하면 작업을 나누고 다시 합치는데 걸리는 시간이 있기 때문입니다.

 

항상 멀티 쓰레드로 처리하는 것이 빠르다고 생각해서는 안됩니다. 반드시 테스트해보고 이득이 있을 때만 멀티 쓰레드로 처리해야 합니다.

 

References

source code : https://github.com/yonghwankim-dev/java_study/tree/main/ch13
[도서] Java의 정석, 남궁 성 지음