💡 개요
오늘은 약 1년 전 유데미에서 수강했던 자바 멀티스레딩의 Thread 종료 기법인 interrupt에 대해 정리해보자.
📕 Thread 종료
interrupt의 사전적 의미는 ‘가로막다, 중단하다’ 이다.
말 그대로 interrupt() 메서드는 현재 실행중인 스레드를 중단시켜 종료할 때 사용한다.
스레드 종료 로직이 따로 필요한 이유는 크게 3가지 이다.
- 스레드는 고정된 리소스(메모리, 커널)를 할당받아야 하고, CPU 시간과 캐시 공간도 사용한다. 따라서 생성한 스레드가 이미 작업을 완료했는데 애플리케이션이 작동중이라면 사용하지 않는 스레드는 정리되어야 하는 것이다.
- 스레드가 작업 도중 응답이 없는 외부 서버에 요청을 계속 보내거나, 내부 문제로 정상적으로 동작하지 않는다면 해당 스레드를 강제로 중지시켜야 한다.
- 애플리케이션 전체를 중지하기 위해서이다. 만약 애플리케이션 내부의 스레드 중, 하나라도 실행되고 있다면 애플리케이션은 종료되지 않는다. 메인 스레드가 종료되었어도, 다른 일반 스레드가 실행되고 있다면 애플리케이션은 종료되지 않는다.
결국 “애플리케이션 종료 = 모든 스레드의 종료” 인 것이다.
🛠️ Thread.interrupt()
Thread를 interrupt, 말 그대로 정지시키는 명령어이다.
public class Main {
public static void main(String [] args) {
Thread thread = new Thread(new BlockingTask());
thread.setName("interruptedThread");
thread.start();
thread.interrupt();
}
private static class BlockingTask implements Runnable {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("interrupt로 인한 스레드 종료");
}
}
}
}
메인 스레드가 실행시킨 interruptedThread 스레드를 interrupt() 메서드를 사용해 종료하는 코드이다.
thread.interrupt() 메서드가 실행되면 InterruptedException이 발생하고 BlockingTask.run() 메서드의 catch가 해당 예외를 처리한다.
🛠️ Thread.setDaemon()
데몬이 설정된 스레드는 백그라운드에서 실행되는 스레드로, 애플리케이션 종료를 막지 않는 스레드이다.
만약 메인 스레드, 일반 스레드가 종료되고 데몬 스레드만 종료되지 않은 경우엔 애플리케이션이 종료되는 것이다.
데몬 스레드를 사용해 처리하기 좋은 작업은 크게 두 가지 있다.
- 백그라운드 작업 (자동저장, 주기적인 리소스 정리(예: 캐시 청소), 로깅, 모니터링)
- 외부 라이브러리를 호출하는 작업
- 왜나하면 외부에선 interrupt 신호를 처리할 방법이 없기 때문이다.
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new LongComputationTask(new BigInteger("10000000"), new BigInteger("10000000")));
thread.setDaemon(true); // 메인 스레드 종료 시 함께 종료될 데몬 스레드로 설정
thread.start();
thread.interrupt(); // 작업중인 계승 스레드에게 중지 요청
}
private static class LongComputationTask implements Runnable {
private BigInteger base;
private BigInteger power;
public LongComputationTask(BigInteger base, BigInteger power) {
this.base = base;
this.power = power;
}
@Override
public void run() {
System.out.println(base + "^" + power + " = " + pow(base, power));
}
private BigInteger pow(BigInteger base, BigInteger power) {
BigInteger result = BigInteger.ONE;
for (BigInteger i = BigInteger.ZERO; i.compareTo(power) != 0; i = i.add(BigInteger.ONE)) {
result = result.multiply(base);
}
return result;
}
}
}
위의 코드는 입력받은 base와 power를 토대로 base^power를 구하는 코드이다. (숫자가 커질 것을 대비해 BigInteger를 사용)
코드를 보면 LongComputationTask 스레드에 base는 10000000, power는 10000000 을 입력했다.
딱 봐도 굉장히 큰 수가 나올 것 같다. 만약 위의 코드에서 데몬 스레드로 설정해주는 코드가 없다면 메인 스레드가 종료되지 않을 것이다.
thread.setDaemon(true); // 메인 스레드 종료 시 함께 종료될 데몬 스레드로 설정
thread.start();
thread.interrupt(); // 작업중인 스레드에게 중지 요청
하지만 위의 코드에선 계승 스레드를 데몬 스레드로 선언했기 때문에 계승 계산 작업이 종료되지 않았어도 interrupt() 요청이 들어오면 작업의 완료 유무에 관계없이 계승 스레드가 중지되고, 애플리케이션이 종료된다.
'개발 일기' 카테고리의 다른 글
[개발 일기] 2025.03.04 - RequestDto에 Getter가 없으면 예외? (0) | 2025.03.04 |
---|---|
[개발 일기] 2025.03.03 - Thread 조정 (동기화) (1) | 2025.03.03 |
[개발 일기] 2025.03.01 - Redis 직렬화 (1) | 2025.03.01 |
[개발 일기] 2025.02.28 - @Profiles, @ActiveProfiles (1) | 2025.02.28 |
[개발 일기] 2025.02.26 - Dangling quantifier '+’ 에러 (0) | 2025.02.26 |