개발/병렬 프로그래밍

자바 병렬 프로그래밍 - 7장 중단 및 종료

bebeside77 2016. 8. 29. 15:14

7 중단 종료

  • 작업을 취소하고 인터럽트를 거는 부분에 대한 개념, 작업이나 서비스가 취소 요청에도 잘 반응하도록 프로그램하는 방법에 대해 알아본다.

 

  • 7.1 작업 중단
  • 7.2 스레드 기반 서비스 중단
  • 7.3 비정상적인 스레드 종료 상황 처리
  • 7.4 JVM 종료

7.1 작업 중단

  • 자바에는 스레드가 작업을 실행하고 있을 때 강제로 멈추도록 하는 방법이 없음

 

  • 대신 인터럽트(interrupt)를 사용할 수 있음. 인터럽트란, 특정 스레드에게 작업을 멈춰달라고 요청하는 것
    (명령이 아닌 요청이다.)

 

  • 실행 중인 스레드를 강제로 멈추게 하기 보다는 작업을 진행하던 스레드에서 스스로 마무리하는 것이 정상적으로 종료할 수 있어서 가장 적절한 방법임

7.1 작업 중단

  • 실행 중인 작업을 취소하고자 하는 요구사항이 생기는 경우들

    1. 사용자가 취소하기를 요청한 경우

    2.
    시간이 제한된 작업

    3.
    애플리케이션 이벤트

    4.
    오류

    5.
    종료

7.1 작업 중단

  • 작업을 실행하는 스레드가 스스로 멈추게 하는 방법 중 가장 기본적인 방법은 취소 상태에 대한 플래그값을 두고 작업에서 주기적으로 확인하는 방법

    => 별로 좋지 않은 방법

 

  • 문제점

    1. 플래그 확인하는 구간이 길어지면 멈추는데 시간이 오래걸릴 수 있다.

    2.
    하지만 작업 중 블로킹 메소드를 호출하는 부분이 있다면 플래그를 확인할 수 없어 작업이 멈추지 않을 수도 있다.

7.1.1 인터럽트

  • 모든 스레드는 인터럽트 상태를 저장하는 boolean 변수가 있다.

 

  • Thread#interrupt로 스레드에 인터럽트 요청을 할 수 있다.
    Thread#isInterrupted
    로 인터럽트 상태 확인 가능

 

  • static 메소드인 Thread.interrupted는 현재 인터럽트 상태를 초기화하기 때문에 주의해야 함

 

  • Thread.sleep, Object.wait 메소드 같은 블로킹 메소드는 인터럽트 상태를 확인해서 인터럽트 걸리면 즉시 리턴됨
    (BlockingQueue.take 메소드도 인터럽트 상태를 주기적으로 체크하여 발생 시 InterruptedException을 던진다)

7.1.1 인터럽트

  • 스레드에 인터럽트가 걸리면 상태변수가 설정된다.

 

  • 하지만 인터럽트 여부 확인 및 그에 대한 처리는 스레드 내에서 알아서 해야하는 것이다.

 

  • 인터럽트 건다고해서 InterruptedException이 발생하는 것은 아니다.

 

  • 예제 7.5 인터럽트를 사용해 작업을 취소

7.1.2 인터럽트 정책

  • 인터럽트에 대한 처리 정책을 세워야 한다.

 

  • 최대한 빠르게 중단시키고 자원정리 및 중단 요청 스레드에게 중단 중 사실을 알려주는게 가장 좋음

 

  • 작업 마무리를 위해서 작업 내에서 단일 연산으로 보호할 수 있는 범위 확인이 필요하다.

 

  • 작업은 스레드 풀과 같이 실행 전담 스레드에 의해 실행되는데, 작업 요청한 스레드에서도 스레드의 인터럽트 상태를 전달받아서 인터럽트 상태에 대응할 수 있도록 해야한다.
    (
    대부분의 블로킹 메소드에서 인터럽트 걸리면 InterruptedException을 던지는 이유)

 

7.1.2 인터럽트 정책

  • 스레드는 해당 스레드를 소유하는 클래스에서만 인터럽트를 걸어야 한다.

 

  • 인터럽트에 대한 처리를 정확히 모르는 스레드에게 함부로 인터럽트를 걸어서는 안됨

 

  • 자바는 선점형 인터럽트를 제공하지 않아 비판적 의견이 있음

 

  • 하지만 인터럽트에 대한 실제 중단 시점을 개발자에 의해 조절할 수 있어 응답성과 안정성을 능동적으로 관리할 수 있는 장점이 있다.

7.1.3 인터럽트에 대한 대응

  • InterruptedException이 발생했을 때 처리하는 실적적 방법은 두 가지

    1.
    발생한 예외를 호출 스택 상위 메소드로 전달
    (메소드 선언부에 throws 선언하거나 예외를 다시 throw)

    2.
    호출 스택 상위 메소드가 처리할 수 있도록 인터럽트 상태 유지
    (Thread.currentThread().interrupt() 호출)

 

  • 블로킹 메소드 호출하는 부분이 없다면 작업 중간중간 인터럽트 상태를 확인해야 인터럽트에 대한 응답 속도를 높일 수 있음

 

  • 응답 속도가 중요한 애플리케이션에서는 인터럽트 응답이 늦고 오래 실행되는 메소드 호출은 자제하는 것이 좋음

7.1.4 예제 : 시간 지정 실행

  • 실행하는 시간을 지정해서 실행 시간 이후 스레드를 중단시키고자 함

 

  • 예제 7.8 방법은 이해하기도 쉽고 구현하기도 간단함

 

  • 하지만 스레드에 인터럽트를 걸때 대상 스레드 인터럽트 정책을 알고있어야 한다는 규칙을 어기고 있다.

 

  • timeRun 메소드는 외부의 어떤 스레드에서도 호출 가능하기 때문.

 

  • 작업 스레드가 인터럽트에 제대로 반응되지 않도록 되어 있으면 멈추지 않을 수도 있음

7.1.4 예제 : 시간 지정 실행

  • 예제 7.8 방법은 이해하기도 쉽고 구현하기도 간단함

 

  • 하지만 스레드에 인터럽트를 걸때 대상 스레드 인터럽트 정책을 알고있어야 한다는 규칙을 어기고 있다.

 

  • timeRun 메소드는 외부의 어떤 스레드에서도 호출 가능하기 때문.

 

  • 작업 스레드가 인터럽트에 제대로 반응되지 않도록 되어 있으면 멈추지 않을 수도 있음

7.1.5 Future 사용해 작업 중단

  • 작업을 중단하려 할 때는 항상 스레드에 직접 인터럽트를 거는 대신 Future.cancel 메소드를 호출해야 한다.

7.1.7 newTaskFor 메소드로 비표준적인 중단 방법 처리

  • Interrupt 메소드를 오버라이드해 표준에 정의되어 있지 않은 작업 중단 방법을 구현

7.2 스레드 기반 서비스 중단

  • 스레드 기반 서비스를 생성한 메소드 보다 생성된 스레드 기반 서비스가 오래 실행될 수 있는 상황이라면, 스레드 기반 서비스에서는 항상 종료시키는 방법을 제공해야 한다.

 

  • 스레드 풀에 들어 있는 모든 작업 스레드는 해당하는 스레드 풀이 소유한다고 볼 수 있고, 따라서 개별 스레드에 인터럽트를 걸어야 하는 상황이 된다면 그 작업은 스레드를 소유한 스레드 풀에서 책임을 저야한다.

 

  • 애플리케이션이 개별 스레드에 직접 액세스하는 대신 스레드 기반 서비스가 스레드의 시작부터 종료까지 모든 기능에 해당하는 메소드를 직접 제공해야 한다.

7.2 스레드 기반 서비스 중단

  • LogWriter에 추가한 안정적인 종료 방법 예제

7.2 스레드 기반 서비스 중단

  • 종료된 이후에도 실행이 중단된 작업이 어떤 것인지 알려주는 ExecutorService 예제

7.2.5 shutdownNow 메소드의 약점

  • 실행되기 시작했지만 아직 완료되지 않은 작업이 어떤 것인지 알아볼 수 있는 방법이 없다.

 

  • 예제 7.21 종료된 이후 실행 중단 작업을 알려주는 방법

    이런 기법이 제대로 동작하려면 개별 작업이 리턴될 때 자신을 실행했던 스레드의 인터럽트 상태를 유지해야 함

7.3 비정상적인 스레드 종료 상황

  • UncaughtExceptionHandler 를 지정해두면 처리하지 못한 예외 상황 때문에 스레드가 종료되는 경우에 호출된다.

 

  • 잠깐 실행하고 마는 애플리케이션이 아닌 이상, 예외가 발생했을 때 로그 파일에 오류를 출력하는 간단한 기능만이라도 확보할 수 있도록 모든 스레드를 대상으로 UncaughtExceptionHandler를 활용해야 한다.

7.4 JVM 종료

  • 종료 훅

 

  • 예정된 절차대로 종료되는 경우에 Runtime.addShutdownHook 메소드를 사용해 등록된 모든 종료 훅을 실행시킨다.

 

  •  하나의 JVM에 여러 개의 종료 훅을 등록할 수 있다.

 

  •  두 개 이상의 종료 훅이 등록되어 있는 경우에 어떤 순서로 훅을 실행하는지에 대해서는 아무런 규칙이 없다.

 

  • 종료 훅은 스레드에 안전하게 만들어야만 한다.

 

  • 종료 훅은 어떤 서비스나 애플리케이션 자체의 여러 부분을 정리하는 목적으로 사용하기 좋다.

 

7.4 JVM 종료

  • 데몬 스레드

 

  • 비데몬 스레드 중 하나라도 종료되지 않았을 경우만 떠있게 되는 스레드
    (데몬 스레드만 존재하면 데몬 스레드들은 종료된다.)

 

  • 데몬 스레드는 finally 블록도 실행되지 않는다.

 

  • 부수적인 작업 처리를 위해 사용(, 가비지 컬렉터 스레드)

 

  • 스레드 생성 시 부모 스레드의 데몬 여부값을 그대로 사용한다.

7.4 JVM 종료

  • finalize 메소드

 

  • 가비지 컬렉터에 수집될 때 객체에 finalize가 정의되어 있으면 호출된다.

 

  • 명시적으로 자원 정리 작업을 하는 용도로 사용

 

  • 실행  여부나 시점에 대한 보장이 없고 성능상 문제가 있을 수 있음

    => try-finally
    구문을 대신 사용하고 finalize는 사용하지 말아라