1. fork & join 프레임웍
- 이 프레임웍은 하나의 작업을 작은 단위로 나눠서 여러 쓰레드가 동시에 처리하는 것을 쉽게
만들어 준다.
- 수행할 적업에 따라 두 클래스로 나눠지는데, 상속받아 구현해야 한다.
클래스 | 설명 |
RecursiveAction | 반환값이 없는 작업을 구현할 때 사용 |
RecursiveTask | 반환값이 있는 작업을 구현할 때 사용 |
public abstract class RecursiveAction extends ForkJoinTask<void>{
...
protected abstract void compute(); // 상속을 통해 이 메서드를 구현해야 한다.
...
}
public abstract class RecursiveTask<V> extends ForkJoinTask<V>{
...
V result
protecteed abstract V compute(); // 상속을 통해 이 메서드를 구현해야 한다.
...
}
- fork()는 해당 작업을 쓰레드의 작업 큐에 넣는 작업을 한다.
( 비동기 메서드)
- join()은 해당 작업의 수행이 끝날 때까지 기다렸다가, 수행이 끝나면 그 결과를 반환한다.
( 동기 메서드 )
- invoke()는 쓰레드 풀과 수행할 작업이 생성되면 작업을 시작하는 메서드이다.
- 쓰레드의 start()와 run()이 있다면, fork&join 프레임웍은 invoke()와 compute()가 있는 것이다.
1-1. fork & join 프레임웍을 이해하기 위한 예제(1)
: from에서 to까지 모두 더하는 예제를 쓰레드 풀을 만들어 작업을 나눈 예제이다.
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class Exercise021 {
public static void main(String[] args) throws Exception {
ForkJoinPool pool = new ForkJoinPool();
//쓰레드 풀 생성
SumTask task = new SumTask(1, 10);
//수행할 작업을 생성
Long result = pool.invoke(task);
//invoke()를 호출해서 작업을 시작
System.out.println(result);
int sum = 0;
}
}
class SumTask extends RecursiveTask<Long>{
long from, to;
public SumTask(long from, long to) {
this.from = from;
this.to = to;
}
public long sum() {
int sum = 0;
for(long i = from; i<=to; i++) {
sum += i;
}
return sum;
}
@Override
protected Long compute() {
long size = to - from +1;
if(size <= 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();
}
}
( 나눠진 작업은 각 쓰레드가 골고루 나눠서 처리한다. )
( fork()는 비동기 메서드이므로, 호출하면 결과를 기다리지 않고 바로 다음 구문이 실행된다. )
( 그러다가 작업을 더이상 나눌 수 없게 되었을 때, compute()의 재귀호출이 끝나고 join()의 결과를 기다렸다가
더해서 결과를 반환한다. )
1-1. fork & join 프레임웍을 이해하기 위한 예제(1)
: 일반 for문과 fork&join 프레임웍의 성능을 비교하는 예제이다.
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class Exercise022 {
static final ForkJoinPool pool = new ForkJoinPool(); // 쓰레드풀을 생성
public static void main(String[] args) {
long from = 1L;
long to = 100_000_000L;
SumTask2 task = new SumTask2(from, to);
long start = System.currentTimeMillis(); // 시작시간 초기화
Long result = pool.invoke(task);
System.out.println("Elapsed time(4 Core):"+(System.currentTimeMillis()-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;
System.out.println("Elapsed time(1 Core):"+(System.currentTimeMillis()-start));
System.out.printf("sum of %d~%d=%d%n", from, to, result);
} // main의 끝
}
class SumTask2 extends RecursiveTask<Long> {
long from;
long to;
SumTask2(long from, long to) {
this.from = from;
this.to = to;
}
public Long compute() {
long size = to - from;
if(size <= 5) // 더할 숫자가 5개 이하면
return sum(); // 숫자의 합을 반환
long half = (from+to)/2;
// 범위를 반으로 나눠서 두 개의 작업을 생성
SumTask2 leftSum = new SumTask2(from, half);
SumTask2 rightSum = new SumTask2(half+1, to);
leftSum.fork();
return rightSum.compute() + leftSum.join();
}
long sum() { // from~to의 모든 숫자를 더한 결과를 반환
long tmp = 0L;
for(long i=from;i<=to;i++)
tmp += i;
return tmp;
}
}
( for문보다 fork()&join 프레임웍으로 계산한 것이 더 빠르게 측정되었다. )
( 책에서는 옛날이다보니 for문이 더 빠르게 나왔지만, 현재 멀티 쓰레드로 처리하는 것이 더 빠르다. )
( 그러므로, 상황에 맞게 더 좋은 성능을 택하는 것이 좋다. )
2. 다른 쓰레드의 작업 훔쳐오기
- fork()가 호출되어 작업 큐에 추가된 작업 역시, compute()에 의해 더 이상 나눠지지 않을 때까지 반복해서
나뉘고, 자신의 작업 큐가 비어있는 쓰레드는 다른 쓰레드의 작업 큐에서 작업을 가져와서 수행한다.
이러한 과정을 '작업 훔쳐오기' 라고 하며, 모두 쓰레드풀에 의해 자동적으로 이루어진다.
'자바 > 프로세스와 쓰레드' 카테고리의 다른 글
volatile (0) | 2022.05.10 |
---|---|
쓰레드의 동기화(3): Lock과 Condition을 이용한 동기화 (0) | 2022.05.08 |
쓰레드의 동기화(2): wait() & notify() (0) | 2022.05.07 |
쓰레드의 동기화(1): Critical Section & Lock, snychronized (0) | 2022.05.07 |
쓰레드의 실행제어 (0) | 2022.02.20 |