자바 5에서 제공하는 Executor 프레임워크와 스레드 풀을 통해 자바는 스레드의 힘을 높은 수준으로 끌어올렸다.
바로 태스크 제출과 실행을 분리하여 제공했기 때문이다. 우리는 스레드 풀을 만들어 놓고, 스레드풀에 태스크만 제출하면 된다!
스레드의 문제점 ㅠㅠ
- 운영체제에 직접 접근한다.
- 생성비용이 비싸다.
- 운영체제 스레드의 숫자는 제한되어 있으며, 이 수를 초과해버리면 자바 애플리케이션이 예상치 못한 방식으로 크래시될 수 있다. (계속 스레드를 생성하는 상황은 절대 나오면 안된다.)
즉, 개발자가 직접 스레드를 다루기에는 신경쓸게 많다는 것이다.
만약에 다양한 기기에서 실행될 수 있는 프로그램을 만든다면 미리 하드웨어 스레드 개수를 추측하지 않는 것이 좋다. 주어진 프로그램에서 사용할 최적의 자바 스레드 개수는 사용할 수 있는 하드웨어 코에의 개수에 따라 달리지기 때문이다. -> 하드웨어, 운영체제, 자바 스레드관련해서는 공부가 더 필요하다.
그만큼 스레드는 다루기가 힘들다. 나도 힘들다.
그럼 이제 스레드 풀에 대해 알아보자.
스레드 풀 그리고 스레드 풀이 좋은 이유
자바 ExecutorService는 태스크를 제출하고 나중에 결과를 수집할 수 있는 인터페이스를 제공한다.
ExecutorService newFixedThreadPool(int nThreads)
위의 팩토리 메서드를 이용하여 스레드 풀을 만들어 사용할 수 있다. 이 메서드는 워커 스레드라 불리는 nThreads를 포함하는 ExecutorService를 만들고 이들을 스레드 풀에 저장한다. 스레드 풀에서 사용하지 않은 스레드로 큐에 들어온 태스크들을 먼저 실행하며 태스크가 실행 종료되면 스레드 풀로 반환한다.
장점 : 하드웨어에 맞는 수의 태스크를 유지함과 동시에 수 천개의 태스크를 스레드 풀에 아무 오버헤드 없이 제출할 수 있다. 즉, 큐의 크기 조정이나 거부 정책 태스크 우선순위 같은 다양한 설정만 하고 우리 개발자들은 태스크(Runnable 혹은 Callable)를 스레드풀에 제공만 하면 된다.
스레드 풀 그리고 스레드 풀이 나쁜 이유
스레드 풀을 사용할 때 두 가지 사항을 주의해야 한다.
- n개의 스레드를 가진 스레드 풀은 오직 n개 만큼의 스레드를 동시에 실행할 수 있다. 초과로 제출된 태스크들은 큐에 저장되며 이전에 태스크 중 하나가 종료되기 전까지 스레드를 할당받지 못한다. 문제는 잠을 자거나 I/O를 기다리거나 네트워크 연결을 기다리는 태스크가 있으면 성능이 급격히 저하될 수 있다는 것이다. (최악의 경우 데드락이 걸릴 수 있음) 블록될 수 있는 태스크는 스레드 풀에 제출하지 않는게 이상적이지만 과연... 가능할까..?
- 중요한 코드를 실행하는 스레드가 죽는 일이 발생하지 않도록 자바에서는 main이 반환하기 전에 모든 스레드의 작업이 끝나길 기다린다. 따라서 프로그램을 종료하기 전에 모든 스레드 풀을 종료하는 습관은 중요하다. (풀의 워커 스레드가 만들어진 다음 다른 태스크 제출을 기다리면서 종료되지 않은 상태일 수 있으므로)
끄적끄적
스레드하면 비동기가 생각난다.. 비동기에관해 살짝 생각해볼 필요가 있다. 비동기 메서드를 사용할 때 어떤 위험성이 있을까?
- 스레드 실행은 메서드를 호출한 다음의 코드와 동시에 실행되므로 데이터 경쟁 문제를 일으키지 않도록 주의해야 한다. (공유 데이터에 관한 문제)
- 기존 실행 중이던 스레드가 종료되지 않은 상황에서 자바의 main() 메서드가 반환하면 어떻게 될까?
- 애플리케이션을 종류하지 못하고 모든 스레드가 실행을 끝낼 때까지 기다린다.
- 애플리케이션 종료를 방해하는 스레드를 강제종료(kill)시키고 애플리케이션을 종료한다.
이들 문제를 피하기 위해서는 애플리케이션에 만든 모든 스레드를 추적하고 애플리케이션을 종료하기 전에 스레드 풀을 포함한 모든 스레드를 종료하는 것이 좋다.
자바에서는 데몬/비데몬으로 스레드를 구분시킬 수 있다.
데몬 스레드는 주 스레드(혹은 애플리케이션)가 종료될 때 강제 종료되는 반면, 비데몬 스레드는 하나의 주 스레드가 된다. main() 메서드는 모든 비데몬 스레드가 종료될 때까지 프로그램을 종료하지 않고 기다린다.
'프로그래밍 노트 > JAVA' 카테고리의 다른 글
[JAVA]병렬 스트림(parallel) vs CompletableFuture non-blocking 코드 만들기 (0) | 2021.09.07 |
---|---|
[JAVA]CompletableFutrue를 사용해보자 (0) | 2021.09.05 |
[Java] LocalDateTime, ZonedDateTime (0) | 2021.07.24 |
[JAVA] 가비지 컬렉터(GarabageCollector,GC) (0) | 2021.01.22 |
[Java] LocalDate, LocalDateTime 날짜 차이 계산하기 (0) | 2020.12.22 |