- CompletionService는 Executor의 기능과 BlockingQueue의 기능을 하나로 모은 인터페이스이다. 완료된 결과값을 쌓아둘 블로킹큐를 생성하고. 작업이 완료된 순서대로 이 큐에 쌓이게 된다.
<중단정책>
- interrupt : 해당 스레드에게 중지를 요청하는것 . 이 요청을 받은 스레드는 스스로 적절하게 종료할수있게끔 행동해야한다
- 가장 기본적인 취소정책은 cancel메소드 호출 -> volatile로 선언한 취소플래그를 true로 변경-> 작업 종료 하는 것이다
- 그러나 블로킹큐의 put , take 같은 블로킹메서드를 호출하는 경우 반복문 작업 내부에서 취소요청이 들어왔는지 확인하지 못하는 경우가 생길 수 있다. 이런 경우 while 반복문의 조건확인 부분에서 인터럽트 여부를 직접확인 하는 방법으로 응답속도를 개선할 수 있다.
<인터럽트에 대한 대응>
- 호출 스택의 상위메소드로 예외를 던져서 상위메소드에서 처리할수있도록 넘긴다. (throws InterruptException 한다)
- 호출 스택의 상단에 위치한 메소드가 직접 처리할 수 있도록 인터럽트 상태를 유지한다. ( Thread.currentThread.interrupt()를 하여 인터럽트 상태 유지. 상위 메서드가 얘를 관리할수있도록 한다.)
- 스레드 풀에 들어있는 스레드에 함부로 인터럽트를 걸면 안된다. 해당 스레드에 인터럽트가 걸리는 시점에 어떤 작업을 실행하고 있을지 알수없기 때문이다. 따라서 작업을 중단하려 할 때는 항상 스레드에 직접 인터럽트를 거는 대신 Future의 cancel메소드를 사용해야 한다.
<스레드 기반 서비스 중단>
- 스레드를 소유하는 객체는 대부분 해당 스레드를 생성한 객체라고 볼 수 있다. 스레드 풀을 예로 들면, 스레드 풀에 들어있는 모든 작업 스레드는 해당하는 스레드 풀이 소유한다고 볼 수 있고, 따라서 개별 스레드에 인터럽트를 걸어야 하는 상황이 된다면 그 작업은 스레드를 소유한 스레드 풀에서 책임을 줘야 한다.
- 애플리케이션이 직접 개별스레드에 액세스하는 대신 스레드 기반 서비스가 스레드의 시작부터 종료까지 모든 기능에 해당하는 메서드를 직접 제공해야 한다. 그러면 애플리케이션이 스레드 기반 서비스만 종료시키면 스레드 기반 서비스는 스스로가 소유한 모든 작업 스레드를 종료시키게 된다. 좋은 예로 ExecutorService인터페이스는 shutdown메소드와 shutdownNow메소드를 제공한다.
<비정상적인 스레드 종료 상황 처리>
-예상할 수없었던 예외로 인하여 스레드가 비정상적으로 종료되는 경우 . 자바에서 기본적으로 제공하는 스레드풀에서는 작업에서 예상치 못한 예외가 발생했을때 해당 스레드가 종료되도록 하면서, try-finally구문을 사용해 스레드가 종료되기 전에 스레드 풀에 종료된다는 사실을 알려 다른 스레드를 대체해 실행할 수 있도록 하고 있다.
- UncaughtExceptionHandler를 디폴트로 셋팅해주거나. 혹은 afterExecute메소드를 활용한다.
- 단 Callable로 작업결과를 받는 경우 ExecutionException에 감싸여진 상태로 예외가 넘어오므로 여기에서 처리해주어야 한다.
<JVM종료>
- ExecutorService를 컴포지션으로 사용하는 클래스의 start()부분에 종료훅을 등록하여 자원을 모두 해제한후 stop할수있도록 한다.
- 종료 훅이 여러개 등록되어 있는 경우에는 여러개의 종료 훅이 서로 동시에 실행 되기 때문에 다른 종료 훅에서 해당 서비스를 사용하고 있었다면(즉 힙메모리에서 해당 객체를 공유하고 있었다면) 이미 다른 곳에서 종료되어 문제가 발생할 수 있다. 이런 경우를 예방 하려면. 서비스별로 종료훅을 등록하기 보다는 모든 서비스를 정리할 수 있는 하나의 종료 훅을 사용해 각 서비스를 의존성에 맞춰 순서대로 정리하는 것도 방법이다. 실제로 서비스 간의 종속성이 명확히 보이는 어플리케이션이라면 종료시점의 마무리 절차를 순차적으로 처리하도록 하여 올바른 순서대로 서비스를 종료하고 마무리할 수 있다.
- 데몬 스레드는 예고 없이 종료될 수 있기 때문에 애플리케이션 내부에서 시작시키고 종료시키며 사용하기에는 그다지 좋은 방법이 아니다.
---
<스레드부족 데드락> Thread Starvation Deadlock
- 특정 자원을 확보하고자 계속해서 대기하거나 풀 내부의 다른 작업이 실행되어야 알 수 있는 조건이 만족하기를 기다리는 것처럼 끝없이 대기할 가능성이 있는 기능을 사용하는 작업이 풀에 등록된 경우 발생할 수 있다.
- 다른 작업에 의존성을 가지고 있는 작업을 실행시키는 경우.
- 스레드 풀의 크기는 직접적으로 지정하는 것 이외에도 스레드 풀에서 필요로 하는 자원이 제한되어 원하는 크기보다 작은 수준에서 동작하는 경우도 있다. 예를 들어 10개짜리 JDBC커넥션풀을 사용해야 한다면. 결국 실제로 수행될 수 있는 양은 JDBC풀의 크기인 10개에 불과한 셈이다.
---
- 적절한 스레드 풀 사이즈
- 집중대응정책 : 적절한 큐 사이즈 조정 및 거부정책
- 스레드 팩토리 : 직접작성하여 커스터마이징할 수 있다. 스레드 이름을 의미있게 짓는다거나 로그기능을 포함하는 스레드를 넘긴다거나..
-----
<데드락>
- 상대가 자원을 놓기만을 기다리는 상태
- JVM은 데드락을 스스로 해결해주지못한다
- 데이터베이스 시스템의 경우 데드락상황에서 복구하는 기능을 가지고있다. (한쪽을 희생양으로 해서 트랜잭션을 강제종료)
- 락의 순서를 제어하여 데드락을 방지한다.
- 암묵적인 락 대신에 명시적인 락을 사용하고, 타임아웃을 지정하여 락을 확보하려는 시점에서 시간제한이 걸리면 이미 확보했던 락을 풀어주고 잠시 기다리다가 다시 작업을 시도해볼수있다.
<오픈호출>
- 락을 전혀 확보하지 않은 상태에서 메소드를 호출하는 것
- 데드락을 미연에 방지하고자 사용할 수 있다
- open call
<라이브 락>
- 특정작업의 결과를 받아와야 다음 단계로 넘어갈 수 있는작업이 실패할수밖에 없는 기능을 계속해서 재시도하는 경우에 볼수있다.
- 메시지를 제대로 전송하지 못했을 때 해당 전송 트랜잭션을 롤백하고 실패한 메시지를 큐의 맨 뒤에 쌓아두는 트랜잭션 메시지 전송 어플리케이션에서 자주 나타난다. 특정 메시지를 큐에서 뽑아 핸들러에 넘겼을때 핸들러는 같은 에러 결과를 내면서 계속해서 호출된다.
- 에러를 너무 완벽하게 처리하고자 회복 불가능한 오류를 회복 가능하다고 판단해 계속해서 재시도 하는 과정에서 나타난다.
댓글 없음:
댓글 쓰기