생산자 소비자 문제를 실무에서 사용할 때는, 결국 소비자가 처리할 수 없을 정도로 생산 요청이 가득 차면 어떻게 할지를 정해야 한다. 개발자가 인지할 수 있게 로그도 남겨야 하고, 사용자에게 현재 시스템에 문제가 있다고 알리는 것도 필요하다. 이런 것을 위해 예외 정책이 필요하다.
ThreadPoolExecutor에 작업을 요청할 때 큐도 가득 차고, 초과 스레드도 더는 할당할 수 없다면 작업을 거절한다. ThreadPoolExecutor는 작업을 거절하는 다양한 정책을 제공한다.
- AbortPolicy: 새로운 작업을 제출할 때 RejectedExecutionException 을 발생시킨다. 기본 정책이다.
- DiscardPolicy: 새로운 작업을 조용히 버린다.
- CallerRunsPolicy: 새로운 작업을 제출한 스레드가 대신해서 직접 작업을 실행한다.
- 사용자 정의(RejectedExecutionHandler): 개발자가 직접 정의한 거절 정책을 사용할 수 있다.
참고로 ThreadPoolExecutor 를 shutdown() 하면 이후에 요청하는 작업을 거절하는데, 이때도 같은 정책이 적용된다.
AbortPolicy



- 작업이 거절되면 RejectedExecutionException을 던진다.
- ThreadPoolExecutor 생성자 마지막에 new ThreadPoolExecutor.AbortPolicy()를 제공하면 된다.
- 참고로 이것이 기본 정책이기 때문에 생략해도 된다.
- 스레드는 1개만 사용한다. 예제를 단순하게 만들기 위해 큐에 작업을 넣지 않도록 SynchronousQueue를 사용한다.
RejectedExecutionException 예외를 잡아서 작업을 포기하거나, 사용자에게 알리거나, 다시 시도하면 된다. 이렇게 예외를 잡아서 필요한 코드를 직접 구현해도 되고, 아니면 다음에 설명한 다른 정책들을 사용해도 된다.
참고: RejectedExecutionHandler

- 마지막에 전달한 AbortPolicy는 RejectedExecutionHandler의 구현체이다.
- ThreadPoolExecutor 생성자는 RejectedExecutionHandler 의 구현체를 전달받는다.
- ThreadPoolExecutor는 거절해야 하는 상황이 발생하면 여기에 있는 rejectedExecution()을 호출한다.
- AbortPolicy는 RejectedExecutionException을 던지는 것을 확인할 수 있다.
DiscardPolicy



- ThreadPoolExecutor 생성자 마지막에 new ThreadPoolExecutor.DiscardPolicy()를 제공하면 된다.
- task2 , task3 은 거절된다. DiscardPolicy는 조용히 버리는 정책이다.
- DiscardPolicy 정책 코드를 보면 왜 버리는지 알 수 있다.
CallerRunsPolicy


- 호출한 스레드가 직접 작업을 수행하게 한다. 이로 인해 새로운 작업을 제출하는 스레드의 속도가 느려질 수 있다.
- task1 은 스레드 풀에 스레드가 있어서 수행한다.
- task2는 스레드 풀에 보관할 큐도 없고, 작업할 스레드가 없다. 거절해야 한다.
- 이때 작업을 거절하는 대신에, 작업을 요청한 스레드에 대신 일을 시킨다.
- task2의 작업을 main 스레드가 수행하는 것을 확인할 수 있다.
이 정책의 특징은 생산자 스레드가 소비자 대신 일을 수행하는 것도 있지만, 생산자 스레드가 대신 일을 수행하는 덕분에 작업의 생산 자체가 느려진다는 점이다. 덕분에 작업의 생산 속도가 너무 빠르다면, 생산 속도를 조절할 수 있다. 원래대로 하면 main 스레드가 task1 , task2 , task3 , task4를 연속해서 바로 생산해야 한다. CallerRunsPolicy 정책 덕분에 main 스레드는 task2를 본인이 직접 완료하고 나서야 task3을 생산할 수 있 다. 결과적으로 생산 속도가 조절되었다.

- r.run() 코드를 보면 별도의 스레드에서 수행하는 것이 아니라 main 스레드가 직접 수행하는 것을 알 수 있다.
- 참고로 ThreadPoolExecutor를 shutdown()을 하면 이후에 요청하는 작업을 거절하는데, 이때도 같은 정책이 적용된다.
- 그런데 CallerRunsPolicy 정책은 shutdown() 이후에도 작업을 수행해 버린다.따라서 shutdown() 조건을 체크해서 이 경우에는 작업을 수행하지 않도록 한다.
사용자 정의(RejectedExecutionHandler)


- 사용자 정의(RejectedExecutionHandler): 사용자는 RejectedExecutionHandler 인터페이스를 구현하여 자신만의 거절 처리 전략을 정의할 수 있다. 이를 통해 특정 요구사항에 맞는 작업 거절 방식을 설정할 수 있다.
- 거절된 작업을 버리지만, 대신에 경로 로그를 남겨서 개발자가 문제를 인지할 수 있도록 작업했다.
'멀티스레드와 동시성' 카테고리의 다른 글
| Ch12. 스레드 풀과 Executor 프레임워크 - Excutor 전략 (0) | 2024.10.29 |
|---|---|
| Ch12. 스레드 풀과 Executor 프레임워크 - Executor 스레드 풀 관리 (0) | 2024.10.16 |
| Ch12. 스레드 풀과 Executor 프레임워크 - ExecutorService 종료 (0) | 2024.10.16 |
| Ch12. 스레드 풀과 Executor 프레임워크 - ExecutorService(작업 컬렉션 처리) (0) | 2024.10.13 |
| Ch12. 스레드 풀과 Executor 프레임워크 - Future (0) | 2024.10.06 |