일반 쓰레드의 작업을 돕는 보조적 역할의 쓰레드.
따라서 일반 쓰레드가 모두 종료되면 데몬 쓰레드도 강제적으로 자동 종료됨
대표적인 예시로 Java
의 Garbage Collector(GC), 워드 프로세서의 자동 저장 등이 있음
데몬 쓰레드는 무한 루프와 조건문을 이용해서 실행 이후 대기하고 있다가 특정 조건이 만족되면 작업을 수행하고 다시 대기 상태로 돌아감.
생성 및 실행 방법은 일반 쓰레드와 동일한데, 생성 이후 setDaemon(true)
를 호출해야함.
데몬 쓰레드가 생성한 쓰레드도 데몬 쓰레드가 됨.
boolean isDaemon
()
데몬 쓰레드인지 확인
void setDaemon
(boolean on)
특정 쓰레드에 대해 호출할 때 매개변수를 true
로 하면 데몬 쓰레드로, false
로 하면 일반 쓰레드로 지정.
class DaemonThreadExample implements Runnable {
static boolean autoSave = false;
public static void main(String[] args) {
Thread t = new Thread(new DaemonThreadExample());
t.setDaemon(true); // 반드시 start()을 호출하기 전에 데몬 쓰레드로 지정
t.start();
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
System.out.println(i); // 1초마다 1부터 차례대로 10까지 숫자를 셈
if (i == 5) autoSave = true; // 5가 되면 autoSave를 true로 변경
}
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {}
if (autoSave) autoSave(); // 3초마다 autoSave의 값을 확인해서 true면 autoSave() 호출
}
}
public void autoSave() {
System.out.println("Your file has been auto-saved.");
}
}
1
2
3
4
5
Your file has been auto-saved.
6
7
8
Your file has been auto-saved.
9
10
약 5초 후부터 autoSave가 true가 되면서 이미 3초마다 autoSave 값을 확인하고 있었던 데몬 쓰레드가 조건문 속 자동 저장 메서드를 호출하는 코드
만약 t
쓰레드가 데몬 쓰레드가 아니었다면, main
쓰레드가 for
문을 모두 돌고 나서 종료되더라도 t
쓰레드가 계속 무한루프로 실행 중이기 때문에 프로그램이 종료되지 않음.
t
가 데몬 쓰레드였기 때문에 자신이 돕고 있는 main
쓰레드가 종료되면서 같이 종료됨.
public class DaemonThreadExample2 {
public static void main(String[] args) throws Exception {
Thread1 t1 = new Thread1("T1");
Thread2 t2 = new Thread2("T2");
t1.start();
t2.start();
}
}
class Thread1 extends Thread {
Thread1(String name) {
super(name);
}
@Override
public void run() {
try {
sleep(5000);
} catch (InterruptedException e) {}
}
}
class Thread2 extends Thread {
Thread2(String name) {
super(name);
}
@Override
public void run() {
Map<Thread, StackTraceElement[]> map = getAllStackTraces();
Iterator<Thread> it = map.keySet().iterator();
int x = 0;
while (it.hasNext()) {
Thread t = it.next();
StackTraceElement[] ste = map.get(t);
System.out.printf("[%d] name: %s, group: %s, daemon: %s%n", ++x, t.getName(), t.getThreadGroup().getName(), t.isDaemon());
for (StackTraceElement el : ste) {
System.out.println(el);
}
System.out.println();
}
}
}
실행 결과
[1] name: Signal Dispatcher, group: system, daemon: true
[2] name: T1, group: main, daemon: false
java.base@17.0.1/java.lang.Thread.sleep(Native Method)
app//Thread1.run(DaemonThreadExample2.java:22)[3] name: Reference Handler, group: system, daemon: true
java.base@17.0.1/java.lang.ref.Reference.waitForReferencePendingList(Native Method)
java.base@17.0.1/java.lang.ref.Reference.processPendingReferences(Reference.java:253)
java.base@17.0.1/java.lang.ref.Reference$ReferenceHandler.run(Reference.java:215)[4] name: Common-Cleaner, group: InnocuousThreadGroup, daemon: true
java.base@17.0.1/java.lang.Object.wait(Native Method)
java.base@17.0.1/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:155)
java.base@17.0.1/jdk.internal.ref.CleanerImpl.run(CleanerImpl.java:140)
java.base@17.0.1/java.lang.Thread.run(Thread.java:833)
java.base@17.0.1/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:162)[5] name: Notification Thread, group: system, daemon: true
[6] name: T2, group: main, daemon: false
java.base@17.0.1/java.lang.Thread.dumpThreads(Native Method)
java.base@17.0.1/java.lang.Thread.getAllStackTraces(Thread.java:1662)
app//Thread2.run(DaemonThreadExample2.java:34)[7] name: Finalizer, group: system, daemon: true
java.base@17.0.1/java.lang.Object.wait(Native Method)
java.base@17.0.1/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:155)
java.base@17.0.1/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:176)
java.base@17.0.1/java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:172)[8] name: Monitor Ctrl-Break, group: main, daemon: true
java.base@17.0.1/java.util.concurrent.ConcurrentSkipListSet.(ConcurrentSkipListSet.java:114)
java.base@17.0.1/java.net.InetAddress.(InetAddress.java:776)
java.base@17.0.1/java.net.InetSocketAddress.(InetSocketAddress.java:229)
java.base@17.0.1/java.net.Socket.(Socket.java:287)
app//com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:44)
getAllStackTrace()
메서드는 작업이 완료되지 않은 모든 쓰레드의 호출 스택 정보를 반환해줌.
작업이 완료되지 않은 쓰레드 == 실행 중이거나 대기 상태에 있는 쓰레드
직접 생성 및 실행한 T1
과 T2
를 포함해 총 8개의 쓰레드가 실행 중 또는 대기 중임을 알 수 있음.
main
쓰레드는 자기 할 일을 모두 마쳤기 때문에 종료 및 제거되어서 실행 결과에는 나타나지 않았음
프로그램이 실행되면, JVM
은 가비지 컬렉션을 수행하는 Finalizer
를 비롯해서 이벤트 처리와 그래픽 처리 등을 위한 데몬 쓰레드를 자동적으로 생성하여 main
이나 system
쓰레드 그룹에 넣는다는 것만 알고 가자.