13-7. 데몬 쓰레드

Hyun Jun·2022년 2월 12일
0

자바의 정석

목록 보기
50/52
post-thumbnail
post-custom-banner

데몬 쓰레드 (Daemon Thread)

일반 쓰레드의 작업을 돕는 보조적 역할의 쓰레드.

따라서 일반 쓰레드가 모두 종료되면 데몬 쓰레드도 강제적으로 자동 종료됨

대표적인 예시로 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() 메서드는 작업이 완료되지 않은 모든 쓰레드의 호출 스택 정보를 반환해줌.

작업이 완료되지 않은 쓰레드 == 실행 중이거나 대기 상태에 있는 쓰레드

직접 생성 및 실행한 T1T2를 포함해 총 8개의 쓰레드가 실행 중 또는 대기 중임을 알 수 있음.

main 쓰레드는 자기 할 일을 모두 마쳤기 때문에 종료 및 제거되어서 실행 결과에는 나타나지 않았음

프로그램이 실행되면, JVM은 가비지 컬렉션을 수행하는 Finalizer를 비롯해서 이벤트 처리와 그래픽 처리 등을 위한 데몬 쓰레드를 자동적으로 생성하여 main이나 system 쓰레드 그룹에 넣는다는 것만 알고 가자.

profile
Back-end Engineer 👨‍💻
post-custom-banner

0개의 댓글