많은 일시적 태스크의 실행을 위한 프로그램을 개발하고 있다면, 쓰레드 풀링(thread pooling)이라는 테크닉을 사용하는 것이 유용할 것이다. 각각의 새로운 태스크에 대해서 쓰레드를 생성했다가 그 태스크가 끝난 후 버리는 대신, 쓰레드의 풀(pool)을 생성하고 그 풀로 하여금 각 태스크를 실행하게 하는 것이다. 풀의 쓰레드가 가능한 상태가 되면 태스크는 즉시 실행되며, 태스트가 끝난 후 쓰레드는 풀로 다시 리턴된다. 아니면, 태스크는 쓰레드가 가능한 상태가 될 때까지 대기한다.
J2SE 5.0에서는 새로운 java.util.concurrent 패키지를 제공하며, 그 안에는 미리 구축된 쓰레드 풀링 프레임워크를 제공하는 병행 유틸리티가 포함되어 있다. java.util.concurrent의 execute인터페이스는 다음과 같이 Runnable오브젝트를 허용하는 단일 메서드인 execute를 제공한다.
쓰레드 풀링 프레임워크를 이용하기 위해 Executor 인스턴스를 생성하고 구동 가능한 태스크에 전달하자.
그 후 Executor 인터페이스의 구현을 찾거나 생성한다. 이 구현은 새로운 쓰레드에서 태스크를 즉시 구동하거나 연속적으로 구동할 수 있다. 예를 들어, 다음은 각 태스크에 대한 새로운 쓰레드를 산출하는 구현이다.
병행 유틸리티에는 또한 많은 일반적 풀링 작용에 대한 지원을 제공하는 ThreadPoolExecutor 클래스가 포함되어있다. 네 개의 ThreadPoolExecutor 컨스트럭터 중 하나를 이용하여 풀의 크기, 지속시간, 쓰레드 팩토리, 거부된 쓰레드에 대한 핸들러 등의 옵션을 지정할 수 있다.
그러나 컨스트럭터를 사용자가 직접 호출할 필요는 없다. 대신에 java.util.concurrentExecutors 클래스가 쓰레드 풀을 호출해준다. 간단한 케이스에서는 사용자가 Executors 클래스의 newFixedThreadPool 메서드를 호출하고 풀 안에서 원하는 몇가지 쓰레드들을 전달한다. 그런 다음 Executor를 확장하는 인터페이스인 ExecutorService 를 이용하여 Runnable 태스크를 실행하던지, 결과를 제출한다. ExecutorService 의 submit 메서드를 호출함으로서 결과를 받을 수 있게 된다. submit 메서드는 또한 태스크가 완료됐는지 확인할 때 사용하는 Future도 리턴한다.
테스트 프로그램을 구동시켜 쓰레드 풀 사용의 데모를 보기로 하자. 첫번째로 다음 프로그램, NamePrinter에서는 언제 태스크를 시작하고, 잠시 멈추는지, 또 언제 완료되는지 알려준다.
다음은 테스트 프로그램 UsePool이다. 싸이즈 3의 쓰레드 풀을 생성하고 10개의 태스크(즉 NamePrinter를 10번 구동)를 추가한다. UsePool 프로그램은 그 후 태스크가 끝나면 shutdown과 awaitTermination를 호출한다. 즉각적인 shutdown을 시도하는 shutdownNow 메서드도 있다. 이 메서드를 사용하면 shutdown메서드를 사용하는 것보다도 빠르게 종료된다. shutdownNow 메서드는 남아있는 Runnable태스크들의 List를 리턴한다.
NamePrinter와 UsePool를 컴파일하고 UsePool을 구동하자. 다음은 샘플 결과를 구동한 것이다. 각각의 구동은 무작위적인 휴식이 있는 유일한 것임을 상기하기 바란다.
처음 세개의 NamePrinter 오브젝트가 빨리 시작되는 것들이다. 실행되고 있는 각각의 NamePrinter 오브젝트가 끝나면 다음 NamePrinter 오브젝트가 시작된다.
J2SE 5.0에서는 더욱 더 많은 쓰레드 풀링 프레임워크 기능들이 있다. 예를 들어 태스크를 "예약"하여 실행할 수 있는 쓰레드 풀의 스케줄을 작성할 수도 있다.
쓰레드 풀과 새로운 병행 유틸리티들에 대한 좀 더 많은 정보는 Concurrency Utilities를 참조하기 바란다.
J2SE 5.0에서는 새로운 java.util.concurrent 패키지를 제공하며, 그 안에는 미리 구축된 쓰레드 풀링 프레임워크를 제공하는 병행 유틸리티가 포함되어 있다. java.util.concurrent의 execute인터페이스는 다음과 같이 Runnable오브젝트를 허용하는 단일 메서드인 execute를 제공한다.
public interface Executor {
public void execute(Runnable command);
}
public void execute(Runnable command);
}
쓰레드 풀링 프레임워크를 이용하기 위해 Executor 인스턴스를 생성하고 구동 가능한 태스크에 전달하자.
Executor executor = ...;
executor.execute(aRunnable1);
executor.execute(aRunnable2);
executor.execute(aRunnable1);
executor.execute(aRunnable2);
그 후 Executor 인터페이스의 구현을 찾거나 생성한다. 이 구현은 새로운 쓰레드에서 태스크를 즉시 구동하거나 연속적으로 구동할 수 있다. 예를 들어, 다음은 각 태스크에 대한 새로운 쓰레드를 산출하는 구현이다.
class MyExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
}
}
public void execute(Runnable r) {
new Thread(r).start();
}
}
병행 유틸리티에는 또한 많은 일반적 풀링 작용에 대한 지원을 제공하는 ThreadPoolExecutor 클래스가 포함되어있다. 네 개의 ThreadPoolExecutor 컨스트럭터 중 하나를 이용하여 풀의 크기, 지속시간, 쓰레드 팩토리, 거부된 쓰레드에 대한 핸들러 등의 옵션을 지정할 수 있다.
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue
ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue
RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
그러나 컨스트럭터를 사용자가 직접 호출할 필요는 없다. 대신에 java.util.concurrentExecutors 클래스가 쓰레드 풀을 호출해준다. 간단한 케이스에서는 사용자가 Executors 클래스의 newFixedThreadPool 메서드를 호출하고 풀 안에서 원하는 몇가지 쓰레드들을 전달한다. 그런 다음 Executor를 확장하는 인터페이스인 ExecutorService 를 이용하여 Runnable 태스크를 실행하던지, 결과를 제출한다. ExecutorService 의 submit 메서드를 호출함으로서 결과를 받을 수 있게 된다. submit 메서드는 또한 태스크가 완료됐는지 확인할 때 사용하는 Future도 리턴한다.
테스트 프로그램을 구동시켜 쓰레드 풀 사용의 데모를 보기로 하자. 첫번째로 다음 프로그램, NamePrinter에서는 언제 태스크를 시작하고, 잠시 멈추는지, 또 언제 완료되는지 알려준다.
public class NamePrinter implements Runnable {
private final String name;
private final int delay;
public NamePrinter(String name, int delay) {
this.name = name;
this.delay = delay;
}
public void run() {
System.out.println("Starting: " + name);
try {
Thread.sleep(delay);
} catch (InterruptedException ignored) { }
System.out.println("Done with: " + name);
}
}
private final String name;
private final int delay;
public NamePrinter(String name, int delay) {
this.name = name;
this.delay = delay;
}
public void run() {
System.out.println("Starting: " + name);
try {
Thread.sleep(delay);
} catch (InterruptedException ignored) { }
System.out.println("Done with: " + name);
}
}
다음은 테스트 프로그램 UsePool이다. 싸이즈 3의 쓰레드 풀을 생성하고 10개의 태스크(즉 NamePrinter를 10번 구동)를 추가한다. UsePool 프로그램은 그 후 태스크가 끝나면 shutdown과 awaitTermination를 호출한다. 즉각적인 shutdown을 시도하는 shutdownNow 메서드도 있다. 이 메서드를 사용하면 shutdown메서드를 사용하는 것보다도 빠르게 종료된다. shutdownNow 메서드는 남아있는 Runnable태스크들의 List를 리턴한다.
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class UsePool {
public static void main(String args[]) {
Random random = new Random();
ExecutorService executor = Executors.newFixedThreadPool(3);
// Sum up wait times to know when to shutdown
int waitTime = 500;
for (int i = 0; i < 10; i++) {
String name = "NamePrinter " + i;
int time = random.nextInt(1000);
waitTime += time;
Runnable runner = new NamePrinter(name, time);
System.out.println("Adding: " + name + " / " + time);
executor.execute(runner);
}
try {
Thread.sleep(waitTime);
executor.shutdown();
executor.awaitTermination(waitTime, TimeUnit.MILLISECONDS);
} catch (InterruptedException ignored) {}
System.exit(0);
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class UsePool {
public static void main(String args[]) {
Random random = new Random();
ExecutorService executor = Executors.newFixedThreadPool(3);
// Sum up wait times to know when to shutdown
int waitTime = 500;
for (int i = 0; i < 10; i++) {
String name = "NamePrinter " + i;
int time = random.nextInt(1000);
waitTime += time;
Runnable runner = new NamePrinter(name, time);
System.out.println("Adding: " + name + " / " + time);
executor.execute(runner);
}
try {
Thread.sleep(waitTime);
executor.shutdown();
executor.awaitTermination(waitTime, TimeUnit.MILLISECONDS);
} catch (InterruptedException ignored) {}
System.exit(0);
}
}
NamePrinter와 UsePool를 컴파일하고 UsePool을 구동하자. 다음은 샘플 결과를 구동한 것이다. 각각의 구동은 무작위적인 휴식이 있는 유일한 것임을 상기하기 바란다.
Adding: NamePrinter 0 / 30
Adding: NamePrinter 1 / 727
Adding: NamePrinter 2 / 980
Starting: NamePrinter 0
Starting: NamePrinter 1
Starting: NamePrinter 2
Adding: NamePrinter 3 / 409
Adding: NamePrinter 4 / 49
Adding: NamePrinter 5 / 802
Adding: NamePrinter 6 / 211
Adding: NamePrinter 7 / 459
Adding: NamePrinter 8 / 994
Adding: NamePrinter 9 / 459
Done with: NamePrinter 0
Starting: NamePrinter 3
Done with: NamePrinter 3
Starting: NamePrinter 4
Done with: NamePrinter 4
Starting: NamePrinter 5
Done with: NamePrinter 1
Starting: NamePrinter 6
Done with: NamePrinter 6
Starting: NamePrinter 7
Done with: NamePrinter 2
Starting: NamePrinter 8
Done with: NamePrinter 5
Starting: NamePrinter 9
Done with: NamePrinter 7
Done with: NamePrinter 9
Done with: NamePrinter 8
Adding: NamePrinter 1 / 727
Adding: NamePrinter 2 / 980
Starting: NamePrinter 0
Starting: NamePrinter 1
Starting: NamePrinter 2
Adding: NamePrinter 3 / 409
Adding: NamePrinter 4 / 49
Adding: NamePrinter 5 / 802
Adding: NamePrinter 6 / 211
Adding: NamePrinter 7 / 459
Adding: NamePrinter 8 / 994
Adding: NamePrinter 9 / 459
Done with: NamePrinter 0
Starting: NamePrinter 3
Done with: NamePrinter 3
Starting: NamePrinter 4
Done with: NamePrinter 4
Starting: NamePrinter 5
Done with: NamePrinter 1
Starting: NamePrinter 6
Done with: NamePrinter 6
Starting: NamePrinter 7
Done with: NamePrinter 2
Starting: NamePrinter 8
Done with: NamePrinter 5
Starting: NamePrinter 9
Done with: NamePrinter 7
Done with: NamePrinter 9
Done with: NamePrinter 8
처음 세개의 NamePrinter 오브젝트가 빨리 시작되는 것들이다. 실행되고 있는 각각의 NamePrinter 오브젝트가 끝나면 다음 NamePrinter 오브젝트가 시작된다.
J2SE 5.0에서는 더욱 더 많은 쓰레드 풀링 프레임워크 기능들이 있다. 예를 들어 태스크를 "예약"하여 실행할 수 있는 쓰레드 풀의 스케줄을 작성할 수도 있다.
쓰레드 풀과 새로운 병행 유틸리티들에 대한 좀 더 많은 정보는 Concurrency Utilities를 참조하기 바란다.
'IT_Programming > Java' 카테고리의 다른 글
JavaTM Web Start (0) | 2008.01.23 |
---|---|
[Tip] Java Main Class를 .exe파일로 실행시키는 방법 (0) | 2008.01.22 |
signed applet 만들기 (0) | 2007.11.22 |
Applet → Refference JavaScript (0) | 2007.11.22 |
[JDBC] CallableStatement (Stored procedure) (0) | 2007.10.28 |