데몬 스레드
데몬(daemon) 스레드는 주 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드이다. 주 스레드가 종료되면 데몬 스레드도 따라서 자동으로 종료된다.
데몬 스레드를 적용한 예로는 워드의 자동 저장, 미디어 플레이어의 동양상 및 음악 재생, 가비지 컬렉터 등이 있는데, 여기에서 주 스레드(워드, 미디어 플레이어, JVM)가 종료되면 데몬 스레드도 같이 종료된다.
스레드를 데몬으로 만들기 위해서는 주 스레드가 데몬이 될 스레드의 setDaemon(true)를 호출하면 된다.
public static void main(String[] args) {
AutoSaveThread thread = new AutoSaveThread();
thread.setDaemon(true);
thread.start();
...
}
다음 예제는 1초 주기로 save() 메서드를 호출하는 AutoSaveThread를 데몬 스레드로 실행시킨다. 그리고 메인 스레드가 3초 후 종료되면 데몬 스레드도 따라서 자동 종료된다.
package ch14;
public class AutoSaveThread extends Thread {
public void save() {
System.out.println("작업 내용을 저장함.");
}
@Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
break;
}
save();
}
}
}
package ch14;
public class DaemonExample {
public static void main(String[] args) {
// TODO Auto-generated method stub
AutoSaveThread autoSaveThread = new AutoSaveThread();
autoSaveThread.setDaemon(true);
autoSaveThread.start();
try { Thread.sleep(3000); } catch(InterruptedException e) {}
System.out.println("메인 스레드 종료.");
}
}
스레드풀
병렬 작업 처리가 많아지면 스레드의 개수가 폭증하여 CPU가 바빠지고 메모리 사용량이 늘어난다. 이에 따라 애플리케이션의 성능 또한 급격히 저하된다. 이렇게 병렬 작업 증가로 인한 스레드의 폭증을 막으려면 스레드풀(ThreadPool)을 사용하는 것이 좋다.
스레드풀은 작업 처리에 사용되는 스레드를 제한된 개수만큼 정해놓고 작업 큐(Queue)에 들어오는 작업들을 스레드가 하나씩 맡아 처리하는 방식이다. 작업 처리가 끝난 스레드는 다시 작업 큐에서 새로운 작업을 가져와 처리한다. 이렇게 하면 작업량이 증가해도 스레드의 개수가 늘어나지 않아 애플리케이션의 성능이 급격히 저하되지 않는다.
스레드풀 생성
자바는 스레드풀을 생성하고 사용할 수 있도록 java.util.concurrent 패키지에서 ExecutorService 인터페이스와 Executors 클래스를 제공하고 있다. Executors의 다음 두 정적 메서드를 이용하면 간단하게 스레드풀인 ExecutorService 구현 객체를 만들 수 있다.
| 메서드명(매개변수) | 초기 수 | 코어 수 | 최대 수 |
| newCachedThreadPool() | 0 | 0 | Integer.MAX_VALUE |
| newFixedThreadPool(int nThreads) | 0 | 생성된 수 | nThreads |
초기 수는 스레드풀이 생성될 때 기본적으로 생성되는 스레드 수를 말하고, 코어 수는 스레드가 증가된 후 사용되지 않는 스레드를 제거할 때 최소한 풀에서 유지하는 스레드 수를 말한다. 그리고 최대 수는 증가되는 스레드의 한도 수이다.
다음과 같이 newCachedThreadPool() 메서드로 생성된 스레드풀의 초기 수와 코어 수는 0개이고, 작업 개수가 많아지면 새 스레드를 생성시켜 작업을 처리한다. 60초 동안 스레드가 아무 작업을 하지 않으면 스레드를 풀에서 제거한다.
ExecutorService executorService = Executors.newCachedThreadPool();
다음과 같이 newFixedThreadPool()로 생성된 스레드풀의 초기 수는 0개 이고, 작업 개수가 많아지면 최대 5개까지 스레드를 생성시켜 작업을 처리한다. 이 스레드풀의 특징은 생성된 스레드를 제거하지 않는다는 것이다.
ExecutorService execotorService = Executors.newFixedThreadPool(5);
위 두 메서드를 사용하지 않고 직접 ThreadPoolExecutor로 스레드풀을 생성할 수도 있다. 다음 예시는 초기 수 0개, 코어 수 3개, 최대 수 100개인 스레드풀을 생성하는 코드이다. 그리고 추가된 스레드가 120초 동안 놀고 있을 경우 해당 스레드를 풀에서 제거한다.
ExecutorService threadPool = new ThreadPoolExecutor(
3, //코어 스레드 개수
100, //최대 스레드 개수
120L, //놀고 있는 시간
TimeUnit.SECONDS, //놀고 있는 시간 단위
new SynchronousQueue<Runnable>() //작업 큐
);
스레드풀 종료
스레드풀의 스레드는 기본적으로 데몬 스레드가 아니기 때문에 main 스레드가 종료되더라도 작업을 처리하기 위해 계속 실행 상태로 남아있다. 스레드풀의 모든 스레드를 종료하려면 ExecutorService의 다음 두 메서드 중 하나를 실행해야 한다.
| 리턴 타입 | 메서드명(매개변수) | 설명 |
| void | shutdown() | 현재 처리 중인 작업뿐만 아니라 작업 큐에 대기하고 있는 모든 작업을 처리한 뒤에 스레드풀을 종료시킨다. |
| List<Runnable> | shutdownNow() | 현재 작업 처리 중인 스레드를 interrupt해서 작업을 중지시키고 스레드풀을 종료시킨다. 리턴값은 작업 큐에 있는 미처리된 작업(Runnable)의 목록이다. |
남아있는 작업을 마무리하고 스레드풀을 종료할 때는 shutdown()을 호출하고, 남아있는 작업과 상관없이 강제로 종료할 때에는 shutdownNow()를 호출하면 된다.
package ch14;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorServiceExample {
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.shutdownNow();
}
}
작업 생성과 처리 요청
하나의 작업은 Runnable 또는 Callable 구현 객체로 표현한다. 둘의 차이점은 작업 처리 완료 후 리턴 값이 있느냐 없느냐이다. 다음은 Runnable과 Callable 구현 객체를 작성하는 방법이다.
//Runnable 익명 구현 객체
new Runnable() {
@Override
public void run() {
//스레드가 처리할 내용
}
}
//Callable 익명 구현 객체
new Callable<T>() {
@Override
public T call() throws Exception {
return T;
}
}
Runnable의 run() 메서드는 리턴값이 없고, Callable()의 call() 메서드는 리턴값이 있다. call()의 리턴 타입은 Callable<T>에서 지정한 T 타입 파라미터와 동일한 타입이어야 한다.
작업 처리 요청이란 ExecutorService 작업 큐에 Runnable 또는 Callable 객체를 넣는 행위를 말한다. 작업 처리 요청을 위해 ExecutorService는 다음 두 가지 메서드를 제공한다.
| 리턴 타입 | 메서드명(매개변수) | 설명 |
| void | execute(Runnable command) | Runnable을 작업 큐에 저장 작업 처리 결과를 리턴하지 않음 |
| Future<T> | submit(Callable<T> task) | Callable을 작업 큐에 저장 작업 처리 결과를 얻을 수 있도록 Future를 리턴 |
다음 예제는 이메일을 보내는 작업으로, 1000개의 Runnable을 생성한 다음 execute() 메서드로 작업 큐에 넣는다. ExecutorService는 최대 5개 스레드로 작업 큐에서 Runnable을 하나씩 꺼내어 run() 메서드를 실행하면서 작업을 처리한다.
package ch14;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class RunnableExecuteExample {
public static void main(String[] args) {
// TODO Auto-generated method stub
String[][] mails = new String[1000][3];
for(int i=0; i<mails.length; i++) {
mails[i][0] = "admin@my.com";
mails[i][1] = "member" + i + "@my.com";
mails[i][2] = "신상품 입고";
}
ExecutorService executorService = Executors.newFixedThreadPool(5);
for(int i=0; i<1000; i++) {
final int idx = i;
executorService.execute(new Runnable() {
@Override
public void run() {
Thread thread = Thread.currentThread();
String from = mails[idx][0];
String to = mails[idx][1];
String content = mails[idx][2];
System.out.println("[" + thread.getName() + "] " + from + " ==> " + to + ": " + content);
}
});
}
executorService.shutdown();
}
}
다음 예제는 자연수를 덧셈하는 작업으로, 100개의 Callable을 생성하고 submit() 메서드로 작업 큐에 넣는다. ExecutorService는 최대 5개 스레드로 작업 큐에서 Callable을 하나씩 꺼내어 call() 메서드를 실행하면서 작업을 처리한다.
package ch14;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableSubmitExample {
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService executorService = Executors.newFixedThreadPool(5);
for(int i=1; i<=100; i++) {
final int idx = i;
Future<Integer> future = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=1; i<=idx; i++) {
sum += i;
}
Thread thread = Thread.currentThread();
System.out.println("[" + thread.getName() + "] 1~" + idx + " 합 계산");
return sum;
}
});
try {
int result = future.get();
System.out.println("\t리턴값: " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
executorService.shutdown();
}
}'Language > JAVA' 카테고리의 다른 글
| [JAVA] Set 컬렉션 (0) | 2024.10.18 |
|---|---|
| [JAVA] List 컬렉션 (0) | 2024.10.17 |
| [JAVA] 스레드 메서드 (0) | 2024.10.17 |
| [JAVA] 멀티 스레드 (2) | 2024.10.17 |
| [JAVA] 타입 파라미터 (0) | 2024.10.16 |