본문 바로가기
Old Posts/Java

[Java] InterruptedException이란?

by A6K 2021. 12. 19.

자바로 코드를 작성할 때 가장 많이 고려되어야 하는 예외 중 하나가 InterruptedException이다. 스레드의 실행을 잠깐 동안 멈추기 위해 사용하는 sleep 코드를 살펴보자

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    /* Do something */
}

코드에서 단순하게 Thread.sleep() 코드만 사용하면 InterruptedException을 처리하지 않았다고 컴파일러가 에러를 발생시킨다. Thread.sleep() 같은 코드뿐만 아니라 자바 프로그램의 다양한 곳에서 InterruptedException이 발생할 수 있다.

Java에서의 Interrupt

자바에서 '인터럽트(Interrupt)'는 스레드를 종료하기 위한 메커니즘이다.

일반적으로 자바 프로그램은 하나의 커다란 작업을 작은 작업들로 나눠서 각각 스레드에 할당한 후 실행한다. 동시에 여러 스레드가 실행되기 때문에 전체 작업을 좀 더 빨리 끝낼 수 있게 된다.

하지만 사용자가 작업을 취소하는 등 전체 작업이 종료되어야 할 경우가 발생할 수 있다. 이 경우 작업을 나누어 실행하고 있던 스레드들의 처리가 끝날 때까지 기다릴 필요는 없다. 스레드가 실행하는 작업이 더 이상 필요 없는 경우라면 실행을 그만하고 리소스들을 정리한 다음 종료하라고 알려줘야 한다. 이때, 인터럽트가 사용된다.

자바에서 한 스레드는 권한이 있다면 다른 스레드에 인터럽트를 보낼 수 있다.

public static void main(String[] args) throws InterruptedException {
    Thread thread = new SmallTaskThread();

    // 작업 스레드 시작
    thread.start();

    Thread.sleep(1000);
		
    // 작업 스레드에게 인터럽트 보냄
    thread.interrupt();
}

위 코드를 보면 뭔가 작업을 실행할 SmallTaskThread를 start() 메서드를 통해 생성한다. 이후 1초간 sleep 했다가 interrupt() 메서드를 이용해 스레드에 인터럽트를 보낸다.

인터럽트를 받은 스레드에는 '인터럽트 상태(Interrupt State)'가 설정된다. 스레드는 자신이 인터럽트되었는지 Thread 클래스의 isInterrupted() 메서드 등을 통해 능동적으로 확인할 수 있다.

if (Thread.currentThread().isInterrupted()) {
    /* 리소스 정리 코드 수행 */
}

만약 어디엔가 블로킹되어 있는 경우를 생각해보자. 스레드의 실행 코드에 isInterrupted()로 인터럽트를 체크하고 종료 로직을 넣어놨지만 스레드의 실행이 어디엔가 블로킹되어 있다면 인터럽트 로직의 실행은 몇 초간 심지어 영원히 실행되지 않을 가능성도 있다.

대표적으로 sleep() 메소드를 이용해 1년을 sleep 한다고 생각해보자. 

Thread.sleep(31536000000);

if (Thread.currentThread().isInterrupted()) {
    /* 리소스 정리 코드 수행 */
}

스레드의 실행을 종료하는 코드가 바로 아래에 있지만 인터럽트 상태를 확인하려면 스레드가 sleep에서 벗어나야 한다. 비슷하게 소켓 채널에서 데이터가 도착하기를 기다리는 경우도 있다. 네트워크 통신 상대방이 데이터를 전송하지 않으면 소켓 채널에서 영원히 데이터를 기다리고 있을 수 있다.

다행히 자바에서는 블로킹 가능한 메소드들은 내부에서 스레드의 인터럽트 상태를 확인한다. 만약 1년을 sleep 하고 있는 동안 외부에서 인터럽트가 도착해 인터럽트 상태가 설정되었다면? InterruptedException이 발생한다.

InterruptedException

이처럼 스레드들이 인터럽트를 주고  받을 때, 인터럽트를 받는 스레드가 블로킹될 수 있는 메서드를 실행하면 InterruptedException이 발생한다. InterruptedException은 checked exception으로 try-catch로 잡아서 처리를 하거나 메서드 시그니처에 명시해서 메서드 호출자에게 처리를 위임해야 한다.

이 때문에 컴파일러 에러를 해결하려고 InterruptedException을 잡아서 무시하는 헬퍼 메서드를 만들어 사용하기도 한다.

public static void sleep(long millis) {
    try {
        Thread.sleep(millis);
    } catch (InterruptedException e) {
        /* No-op */
    }
}

하지만 특별히 이렇게 해야 할 이유가 없다면 InterruptedException을 무시하는 동작은 사용하지 말자.

InterruptedException은 반드시 적절하게 처리되어야 한다. 만약 이런 식으로 InterruptedException을 무시하는 코드가 여기저기 있다면, 실행하다가 [Ctrl] + C 버튼을 눌러서 실행을 종료하려고 해도 작성한 코드는 종료 명령을 귓등으로도 안 듣게 된다. 결국 kill -9 명령으로 강제 종료해야 할 것이다. 만약 그 와중에 점유하던 리소스가 있었다면 제대로 정리 안된 상태로 남게 될 수도 있다.

 

댓글