이번엔 스레드 10개를 이용해서 1-100까지의 합을 구하는 구현을 해보려고 한다.
결과물
[pool-1-thread-1 ] n = 2, sum = 3
[pool-1-thread-10 ] n = 10, sum = 55
[pool-1-thread-9 ] n = 9, sum = 45
[pool-1-thread-8 ] n = 8, sum = 36
[pool-1-thread-2 ] n = 5, sum = 15
[pool-1-thread-3 ] n = 1, sum = 1
[pool-1-thread-5 ] n = 4, sum = 10
[pool-1-thread-7 ] n = 7, sum = 28
[pool-1-thread-6 ] n = 6, sum = 21
[pool-1-thread-4 ] n = 3, sum = 6
[pool-1-thread-6 ] n = 19, sum = 190
[pool-1-thread-7 ] n = 18, sum = 171
[pool-1-thread-5 ] n = 17, sum = 153
[pool-1-thread-3 ] n = 16, sum = 136
[pool-1-thread-2 ] n = 15, sum = 120
[pool-1-thread-8 ] n = 14, sum = 105
[pool-1-thread-9 ] n = 13, sum = 91
[pool-1-thread-10 ] n = 12, sum = 78
[pool-1-thread-10 ] n = 28, sum = 406
[pool-1-thread-1 ] n = 11, sum = 66
[pool-1-thread-10 ] n = 29, sum = 435
[pool-1-thread-9 ] n = 27, sum = 378
[pool-1-thread-8 ] n = 26, sum = 351
[pool-1-thread-2 ] n = 25, sum = 325
[pool-1-thread-3 ] n = 24, sum = 300
[pool-1-thread-5 ] n = 23, sum = 276
[pool-1-thread-7 ] n = 22, sum = 253
[pool-1-thread-6 ] n = 21, sum = 231
[pool-1-thread-6 ] n = 38, sum = 741
[pool-1-thread-4 ] n = 20, sum = 210
[pool-1-thread-6 ] n = 39, sum = 780
[pool-1-thread-7 ] n = 37, sum = 703
[pool-1-thread-5 ] n = 36, sum = 666
[pool-1-thread-3 ] n = 35, sum = 630
[pool-1-thread-2 ] n = 34, sum = 595
[pool-1-thread-8 ] n = 33, sum = 561
[pool-1-thread-9 ] n = 32, sum = 528
[pool-1-thread-10 ] n = 31, sum = 496
[pool-1-thread-1 ] n = 30, sum = 465
[pool-1-thread-10 ] n = 48, sum = 1,176
[pool-1-thread-9 ] n = 47, sum = 1,128
[pool-1-thread-8 ] n = 46, sum = 1,081
[pool-1-thread-2 ] n = 45, sum = 1,035
[pool-1-thread-3 ] n = 44, sum = 990
[pool-1-thread-5 ] n = 43, sum = 946
[pool-1-thread-7 ] n = 42, sum = 903
[pool-1-thread-6 ] n = 41, sum = 861
[pool-1-thread-4 ] n = 40, sum = 820
[pool-1-thread-4 ] n = 58, sum = 1,711
[pool-1-thread-4 ] n = 59, sum = 1,770
[pool-1-thread-6 ] n = 57, sum = 1,653
[pool-1-thread-7 ] n = 56, sum = 1,596
[pool-1-thread-5 ] n = 55, sum = 1,540
[pool-1-thread-3 ] n = 54, sum = 1,485
[pool-1-thread-2 ] n = 53, sum = 1,431
[pool-1-thread-8 ] n = 52, sum = 1,378
[pool-1-thread-9 ] n = 51, sum = 1,326
[pool-1-thread-10 ] n = 50, sum = 1,275
[pool-1-thread-1 ] n = 49, sum = 1,225
[pool-1-thread-10 ] n = 68, sum = 2,346
[pool-1-thread-9 ] n = 67, sum = 2,278
[pool-1-thread-8 ] n = 66, sum = 2,211
[pool-1-thread-8 ] n = 72, sum = 2,628
[pool-1-thread-8 ] n = 73, sum = 2,701
[pool-1-thread-8 ] n = 74, sum = 2,775
[pool-1-thread-8 ] n = 75, sum = 2,850
[pool-1-thread-8 ] n = 76, sum = 2,926
[pool-1-thread-8 ] n = 77, sum = 3,003
[pool-1-thread-8 ] n = 78, sum = 3,081
[pool-1-thread-2 ] n = 65, sum = 2,145
[pool-1-thread-3 ] n = 64, sum = 2,080
[pool-1-thread-5 ] n = 63, sum = 2,016
[pool-1-thread-7 ] n = 62, sum = 1,953
[pool-1-thread-6 ] n = 61, sum = 1,891
[pool-1-thread-4 ] n = 60, sum = 1,830
[pool-1-thread-6 ] n = 84, sum = 3,570
[pool-1-thread-7 ] n = 83, sum = 3,486
[pool-1-thread-5 ] n = 82, sum = 3,403
[pool-1-thread-3 ] n = 81, sum = 3,321
[pool-1-thread-2 ] n = 80, sum = 3,240
[pool-1-thread-8 ] n = 79, sum = 3,160
[pool-1-thread-8 ] n = 91, sum = 4,186
[pool-1-thread-9 ] n = 71, sum = 2,556
[pool-1-thread-8 ] n = 92, sum = 4,278
[pool-1-thread-8 ] n = 94, sum = 4,465
[pool-1-thread-10 ] n = 70, sum = 2,485
[pool-1-thread-1 ] n = 69, sum = 2,415
[pool-1-thread-10 ] n = 96, sum = 4,656
[pool-1-thread-8 ] n = 95, sum = 4,560
[pool-1-thread-9 ] n = 93, sum = 4,371
[pool-1-thread-2 ] n = 90, sum = 4,095
[pool-1-thread-3 ] n = 89, sum = 4,005
[pool-1-thread-5 ] n = 88, sum = 3,916
[pool-1-thread-7 ] n = 87, sum = 3,828
[pool-1-thread-6 ] n = 86, sum = 3,741
[pool-1-thread-4 ] n = 85, sum = 3,655
[pool-1-thread-9 ] n = 100, sum = 5,050
[pool-1-thread-8 ] n = 99, sum = 4,950
[pool-1-thread-10 ] n = 98, sum = 4,851
[pool-1-thread-1 ] n = 97, sum = 4,753
1 + ... + 100 = 5,050
전 포스팅에서 설명했던 동기화 방식인
AtomicType 과 synchroized 를 두가지를 각각 사용해서 구현해보려고 한다.
AtomicInteger
public class AdderUseAtomicRefer {
private static final DecimalFormat FORMATTER = new DecimalFormat( "###,###,##0" );
private final AtomicInteger sum = new AtomicInteger();
public int add( int number ) {
return sum.addAndGet( number );
}
public int getAnswer() {
return sum.get();
}
public static class Worker implements Runnable {
private final AtomicInteger count;
private final AdderUseAtomicRefer adder;
private final int max;
public Worker( AtomicInteger count, AdderUseAtomicRefer adder, int max ) {
this.count = count;
this.adder = adder;
this.max = max;
}
@Override
public void run() {
while ( count.get() < max ) {
int n = count.incrementAndGet();
int sum = adder.add( n );
System.out.printf( "[%-20s] n = %11s, sum = %11s\n", Thread.currentThread().getName(), FORMATTER.format( n ), FORMATTER.format( sum ) );
try {
Thread.sleep( 5 );
} catch ( InterruptedException e ) {
System.err.println( "Canceled Job : " + Thread.currentThread().getName() );
return;
}
}
}
}
private static final int MAX_THREADS = 10;
public static void main( String[] args ) throws ExecutionException, InterruptedException {
final AtomicInteger count = new AtomicInteger();
final AdderUseAtomicRefer adder = new AdderUseAtomicRefer();
final int max = 100;
ExecutorService pool = Executors.newFixedThreadPool( MAX_THREADS );
List< Future< ? > > futures = new ArrayList<>( MAX_THREADS );
for ( int i = 0; i < MAX_THREADS; i++ ) {
futures.add( pool.submit( new Worker( count, adder, max ) ) );
}
for ( Future< ? > future : futures ) {
future.get();
}
pool.shutdown();
System.out.println( FORMATTER.format( 1 ) + " + ... + " + FORMATTER.format( max ) + " = " + FORMATTER.format( adder.getAnswer() ) );
}
}
먼저 멀티스레드를 생성하기 위해 Runnable 인터페이스를 implements 해준다.
또는 Thread 클래스를 extends해주는 방법도 있지만 Runnable을 사용하겠다.
자세한 사용법은 검색하면 많이 나오니 생략,,
Runnable을 상속받으면 안에는 run() 클래스가 구현되어야 한다.
먼저 Worker 클래스를 implements 시켜주고, private 와 final 키워드를 이용해 캡슐화, Worker에 대한 생성자를 만들어준다.
while 문을 이용해 매개변수로 넣어준 max 값을 이용해 원하는 횟수만큼 반복시키고
AtomicInteger를 이용해 count의 횟수를 원자적으로(동기화 된 상태로) 1 증가시켜준다.
그리고 클래스의 add 함수를 호출해주고 증가된 값을 넣어준다.
이제 준비가 됐으니 main 함수에서 멀티스레드를 생성&호출해주면 되겠다.
0~100을 1씩 저장할 AtomicInter count
클래스 생성자,
최대값을 지역변수로 선언해준다.
그 후 ExcutorServie 클래스를 이용해 스레드를 생성해준다.
newFixedThreadPool(int) : 인자 개수만큼 고정된 쓰레드풀을 만듭니다.
newCachedThreadPool(): 필요할 때, 필요한 만큼 쓰레드풀을 생성합니다. 이미 생성된 쓰레드를 재활용할 수 있기 때문에 성능상의 이점이 있을 수 있습니다.
newScheduledThreadPool(int): 일정 시간 뒤에 실행되는 작업이나, 주기적으로 수행되는 작업이 있다면 ScheduledThreadPool을 고려해볼 수 있습니다.
newSingleThreadExecutor(): 쓰레드 1개인 ExecutorService를 리턴합니다. 싱글 쓰레드에서 동작해야 하는 작업을 처리할 때 사용합니다.
Future란?
비동기적인 연산의 결과를 표현하는 클래스다.
Future를 이용하면 멀티쓰레드 환경에서 처리된 어떤 데이터를 다른 쓰레드에 전달할 수 있고
내부적으로 Thread-Safe 하도록 구현되었기 때문에 synchronized block을 사용하지 않아도 된다.
futures 를 ArrayList에 넣고 지정한 스레드 수만큼 반복한다.
ExecutorSerivce 의 submit으로 멀티스레드 처리 할 작업을 예약해준다.
submit은 람다식을 이용해 호출 할 수 있다. 0~100까지 ++할 AtomicInteger와 클래스 생성자, count 최대값을 매개변수로 넣어주자.
그 다음 ExecutorSerivce의 get()을 이용해 계산이 완료되면 값을 받는다.
작업이 완료되면 shutdown으로 스레드풀을 종료시킨다.
shutdown()은 더 이상 스레드풀에 작업을 추가하지 못하도록 한다. 처리 중인 Task가 모두 완료되면 스레드풀을 종료시킨다.
이번엔 synchroized를 이용하는 방법이다.
public class AdderUseSynchronized {
private static final DecimalFormat FORMATTER = new DecimalFormat( "###,###,##0" );
private int sum;
public synchronized int add( int number ) {
return sum += number;
}
public synchronized int getAnswer() {
return sum;
}
public static class Worker implements Runnable {
private final AtomicInteger count;
private final AdderUseSynchronized adder;
private final int max;
public Worker( AtomicInteger count, AdderUseSynchronized adder, int max ) {
this.count = count;
this.adder = adder;
this.max = max;
}
@Override
public void run() {
while ( count.get() < max ) {
int n = count.incrementAndGet();
int sum = adder.add( n );
System.out.printf( "[%-20s] n = %11s, sum = %11s\n", Thread.currentThread().getName(), FORMATTER.format( n ), FORMATTER.format( sum ) );
try {
Thread.sleep( 5 );
} catch ( InterruptedException e ) {
System.err.println( "Canceled Job : " + Thread.currentThread().getName() );
return;
}
}
}
}
private static final int MAX_THREADS = 4;
public static void main( String[] args ) throws ExecutionException, InterruptedException {
final AtomicInteger count = new AtomicInteger();
final AdderUseSynchronized adder = new AdderUseSynchronized();
final int max = 100;
ExecutorService pool = Executors.newFixedThreadPool( MAX_THREADS );
List< Future< ? > > futures = new ArrayList<>( MAX_THREADS );
for ( int i = 0; i < MAX_THREADS; i++ ) {
futures.add( pool.submit( new Worker( count, adder, max ) ) );
}
for ( Future< ? > future : futures ) {
future.get();
}
pool.shutdown();
System.out.println( FORMATTER.format( 1 ) + " + ... + " + FORMATTER.format( max ) + " = " + FORMATTER.format( adder.getAnswer() ) );
}
}
크게 달라지는 점은 없다.
단 기존에 AtomicInteger의 동기화 함수를 이용하지 않고 synchroized를 메서드에 자체에 선언해 동기화를 해줬다.
객체 자체에 동기화를 적용하는 것과 메서드에 동기화를 적용하는 것의 차이이다.
만드는 프로그램의 특성에 따라 여러 동기화 기법 중 가장 적합한 것을 적용하기 위해 여러 방법들을 숙지해놓는 것이 좋을 것 같다.
'JAVA > JAVA 병렬프로그래밍' 카테고리의 다른 글
JAVA 병렬 프로그래밍 [5] - 화장실 스케쥴링 (1) | 2022.06.06 |
---|---|
JAVA 병렬 프로그래밍 [4] - Sharing Objects ( 객체 공유 ) (0) | 2022.06.05 |
JAVA 병렬 프로그래밍 [2] - Thread Safety (0) | 2022.06.05 |
JAVA 병렬 프로그래밍 [1] (0) | 2022.04.16 |