자바의 정석 책을 공부하고 정리한 글입니다!
혹시라도 틀린 부분이 있다면 친절하게 알려주세요.
감사합니다!
CH13. 쓰레드(Thread) (2)
데몬 쓰레드 (daemon thread)
데몬 쓰레드는 다른 일반 쓰레드의 작업을 돕는 보조적인 백그라운드 쓰레드입니다. 모든 일반 쓰레드가 종료되면, 더 이상 보조 대상이 없기 때문에 JVM은 남아 있는 데몬 쓰레드를 강제 종료시키고 프로세스를 종료합니다.
데몬 스레드는 스스로 작업을 마치면 종료됩니다. 단지 일반 스레드가 모두 종료되면, 작업이 남아 있어도 강제 종료당할 수 있다는 점이 특징입니다.
데몬 쓰레드의 예로는 가비지 컬렉터, 워드프로세서의 자동저장, 화면자동갱신 등이 있습니다.
데몬 쓰레드는 일반 쓰레드와 작성 방식과 실행 방식은 동일하지만, start() 메서드 호출 전에 setDaemon(true)를 호출함으로써 데몬 쓰레드로 설정해야 합니다. 그렇지 않으면 IllegalThreadStateException이 발생합니다.
일반적으로 데몬 쓰레드는 무한 루프와 조건문을 활용해 대기 상태를 유지하다가 특정 조건이 만족되면 작업을 수행하고 다시 대기하는 방식으로 작성됩니다. 또한, 데몬 쓰레드가 생성한 쓰레드는 자동으로 데몬 쓰레드가 됩니다.
- boolean isDaemon(): 쓰레드가 데몬 쓰레드인지 확인한다. 데몬 쓰레드일 경우 true를 반환한다.
- void setDaemon(boolean on): 쓰레드를 데몬 쓰레드 또는 사용자 쓰레드로 변경한다. on의 값이 true일 경우, 데몬 쓰레드가 된다.
다음은 데몬 쓰레드에 대한 예제입니다.
class Main implements Runnable {
static boolean autoSave = false;
public static void main(String[] args) throws IOException {
Thread t = new Thread(new Main());
t.setDaemon(true);
t.start();
for(int i = 1; i <= 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
System.out.println(i);
if(i == 5)
autoSave = true;
}
}
@Override
public void run() {
while(true) {
try{
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {}
// autoSave의 값이 true이면 autoSave() 호출
if(autoSave) {
autoSave();
}
}
}
private void autoSave() {
System.out.println("작업파일이 자동 저장되었습니다.");
}
}

위 코드의 실행 흐름은 다음과 같습니다:
- main() 쓰레드가 실행된다.
- 새로운 쓰레드 t를 생성하고, setDaemon(true)로 데몬 스레드로 설정한 후 start()한다.
- t는 즉시 실행되며, 내부에서 3초 대기 후 autoSave 를 확인하는 루프에 진입한다. (단, 실제 실행 시점은 OS 스케줄러에 따라 다를 수 있다.)
- main() 쓰레드는 1초마다 숫자를 출력하며, 총 10초 동안 실행된다.
- 숫자 출력 중 5초가 되었을 때 autoSave = true로 설정된다.
- 이후 데몬 쓰레드는 3초마다 깨어나 조건을 확인하고, true인 경우 autoSave()를 호출한다.
- main() 스레드가 종료되면, 데몬 스레드도 함께 강제 종료된다.
만약 t.setDaemon(true)를 생략하면 t는 일반 쓰레드가 됩니다. 따라서 main() 쓰레드가 종료되더라도 무한 루프를 실행 중인 t 스레드는 계속 실행되기 때문에 JVM은 종료되지 않습니다.
그 결과, autoSave()가 반복적으로 호출되며 프로그램은 끝나지 않고 계속 실행됩니다.
쓰레드의 실행제어
쓰레드 프로그래밍이 어려운 이유는 동기화(synchronization)와 스케줄링(scheduling) 때문입니다.
동기화: 여러 쓰레드가 공유 자원에 동시에 접근할 때 데이터의 일관성을 유지하기 위해 사용되는 제어 기법
스케줄링: CPU가 여러 쓰레드 또는 프로세스 중 어떤 것을 언제 실행시킬지 결정하는 과정
쓰레드는 실행이 되면, 다음 중 하나의 상태로 존재하게 됩니다.

다음은 쓰레드의 상태 변화 과정에 대한 설명입니다:
- Thread 객체를 생성하면 NEW 상태가 된다.
- start() 메서드를 호출하면 RUNNABLE 상태로 전환되어 실행 대기열에 등록된다.
- 운영체제가 CPU를 할당하면 쓰레드는 RUNNING 상태에서 실제로 작업을 수행한다.
- 실행 도중 sleep(), wait(), join() 등의 메서드를 만나면 쓰레드는 WAITING 또는 TIMED_WAITING 상태로 전환되어 일시 정지된다.
- 시간이 만료되거나 notify(), interrupt() 등의 호출로 일시정지에서 벗어나면 다시 RUNNABLE 상태로 돌아간다.
- 작업이 모두 끝나면 쓰레드는 TERMINATED 상태가 되어 소멸된다.
쓰레드의 동기화
싱글쓰레드 프로세스의 경우 프로세스 내에서 단 하나의 쓰레드만 작업하기 때문에 프로세스의 자원을 가지고 작업하는데 별문제가 없습니다. 하지만 멀티쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에 서로의 작업에 영향을 주게 됩니다.
예를 들어 쓰레드 A가 작업 도중 쓰레드 B에게 제어권을 넘기면, B는 A가 사용 중이던 공유 데이터를 임의로 변경할 수 있습니다. 이 경우 A가 다시 작업을 재개했을 때, 원래 의도와는 다른 결과가 나올 수 있습니다.
이러한 상황을 방지하기 위해서는 한 쓰레드가 특정 작업을 끝마치기 전까지 다른 쓰레드의 방해를 받지 않도록 하는 것이 필요합니다. 그래서 도입된 개념이 바로 임계 영역(critical section)과 잠금(lock) 입니다.
공유 데이터를 다루는 코드를 임계 영역으로 지정하고 이 영역에 진입하기 위해서는 해당 데이터가 가진 락(lock)을 먼저 획득해야 합니다. 락을 획득한 단 하나의 쓰레드만이 임계 영역의 코드를 실행할 수 있으며, 작업을 마친 뒤 락을 반납해야 다른 스레드가 락을 획득하고 임계 영역에 진입할 수 있습니다.
이처럼 한 쓰레드의 작업을 다른 쓰레드가 간섭하지 못하도록 제어하는 것을 쓰레드 동기화(Synchronization)라고 합니다.
락을 보유한 쓰레드가 선점당하면 어떤 일이 벌어질까?
임계 영역을 사용하는 쓰레드가 OS 스케줄러에 의해 선점되는 경우가 있습니다.
예를 들어 쓰레드1이 임계 영역에 접근하기 위해 lock을 획득했고 이 작업은 약 10초가 소요된다고 가정하겠습니다. 하지만 운영체제의 스케줄러가 쓰레드1에 대한 작업시간을 5초로 주게 되면, 쓰레드1은 5초만 작업하고 일시 중단하게 됩니다. 그 다음 순서로 쓰레드2가 실행되는데 이 쓰레드 또한 동일한 임계 영역을 사용하려 합니다. 이런 경우에는 어떻게 동작하게 될까요?
- 쓰레드1의 작업 가능 시간은 5초이므로 5초 동안만 CPU를 할당하고 이후 제어권을 회수한다.
- 이후 쓰레드2가 실행된다.
- 쓰레드2는 동일한 임계 영역에 진입하려고 하지만, 이미 쓰레드1이 락을 갖고 있기 때문에 BLOCKED 상태로 대기한다.
- 일정 시간이 지나 스케줄러가 다시 쓰레드1에게 CPU를 재할당하면, 쓰레드1은 나머지 작업을 마친 후 락을 반납한다.
- 쓰레드1이 락을 반납하면, 대기 중이던 쓰레드2가 락을 획득하고 임계 영역에 진입할 수 있게 된다.
자바에서는 쓰레드의 동기화를 지원하기 위해 다음과 같은 방법을 사용할 수 있습니다.
- synchronized를 이용한 동기화
- Lock과 Condition을 이용한 동기화 (JDK1.5부터 지원)
'도서 정리 > 자바의 정석' 카테고리의 다른 글
[자바의 정석] CH14 람다와 스트림 (0) | 2025.05.12 |
---|---|
[자바의 정석] CH13 쓰레드 (1) (1) | 2025.05.05 |
[자바의 정석] CH12 지네릭스, 열거형, 애너테이션 (1) | 2025.04.30 |
[자바의 정석] CH11 컬렉션 프레임워크(2) (1) | 2025.04.25 |
[자바의 정석] CH11 컬렉션 프레임워크(1) (2) | 2025.04.23 |