EnjoyCSStudy / CS-Knowledge-Source

0 stars 0 forks source link

240131 Java & Spring Interview Question #14

Open skydreamer21 opened 5 months ago

skydreamer21 commented 5 months ago

Java & Spring 관련 면접 질문 달아주세요!

이전 질문들

skydreamer21 commented 5 months ago

java 8, 11, 17 버전의 주요 특징에 대해서 설명해주세요.

BaeYoungSuk commented 5 months ago

스프링 프레임워크와 스프링 부트의 차이에 대해 설명해주세요.

soun997 commented 5 months ago

Bean 생명주기에 대해 설명해주세요.

rt3310 commented 5 months ago

스프링이 여러 요청을 처리하는 방법을 설명해주세요

skydreamer21 commented 5 months ago

java 8, 11, 17 버전의 주요 특징에 대해서 설명해주세요.

📎 Java8

람다 표현식

스트림 API

기본 메서드와 정적 메서드

Optional 추가

Permanent Generation Area 제거

Java8부터는 Permanent Generation이 Metaspace로 대체되었습니다. Metaspace는 런타임 시 메모리 요구 사항에 따라 자체 크기를 조정하며, 필요하다면 MaxMetaspaceSize 매개변수를 설정하여 Metaspace의 양을 조절할 수 있습니다.

📝 Permanent Generation

📝 Metaspace

📎 Java11

String 클래스에 새로운 메소드 추가

람다에서 로컬 변수 Var 사용

자바 파일 실행

Garbage Collector

📎 Java17

Record Data class 추가

향상된 의사 난수 생성기

텍스트 블록 기능 추가

봉인 클래스(Sealed Class) 정식 추가

ZGC 도입


스프링 프레임워크와 스프링 부트의 차이에 대해 설명해주세요.

스프링 프레임워크 IoC 컨테이너와 자체적은 MVC 프레임워크, 광범위한 모듈 등을 제공함으로써 웹 어플리케이션의 효과적인 개발을 돕습니다. 이렇게 Spring 프레임워크가 제공하는 수많은 기능을 이용해서 유연하게 어플리케이션을 개발할 수 있지만 이에 따라 수많은 설정과 구성이 필요합니다. 스프링 부트는 스프링 프레임워크를 기반으로 개발자가 어플리케이션을 빠르게 시작하고 실행할 수 있도록 기본값과 내장 서버를 포함한 자동 구성을 제공합니다. 이를 통해 개발자를 어플리케이션을 빠르게 개발하고 배포할 수 있게 됩니다.

정리하면, 스프링 프레임워크는 복잡한 애플리케이션 개발 및 커스터마이징에 적합하며, 스프링 부트는 빠른 개발과 설정의 간소화에 중점을 둡니다.


Bean 생명주기에 대해 설명해주세요.

먼저 스프링 컨테이너는 Bean의 설정과 구성을 파악하여 Bean을 생성합니다. 이 후 생성한 Bean에 대해 의존성을 주입합니다. 이후 Bean 의 초기화 콜백이 수행되면서 @PostConstruct 어노테이션이 붙은 메서드나 사전 설정한 초기화 메서드가 실행됩니다. 이후 Bean 의 소멸시점에서 소멸 전 콜백함수가 수행됩니다. @PreDestory 어노테이션이 붙은 메서드 혹은 사전에 등록한 메서드가 실행되고 Bean이 소멸됩니다.


스프링이 여러 요청을 처리하는 방법을 설명해주세요

웹 서버로부터 클라이언트의 HTTP 요청을 Dispatcher Servlet이 받습니다. Dispatcher Servlet은 요청을 어떤 컨트롤러가 처리할지 결정하기 위해 HandlerMapping을 사용합니다. HandlerMapping은 URL과 매칭되는 컨트롤러를 선택해 Dispatcher Servlet에게 다시 전달합니다. Dispatcher Servlet은 해당 컨트롤러의 비지니스 로직 실행 작업을 Handler Adapter에게 위임합니다. HandlerAdapter는 컨트롤러의 비지니스 로직을 호출하고 결과를 ModelAndView 객체에 담아서 Dispatcher Servlet에게 보냅니다. Dispatcher Servlet은 ViewResolver를 이용하여 결과를 보여줄 View를 가져오고 View에서 생성된 응답을 웹서버를 통해 클라이언트에게 반환합니다.

soun997 commented 5 months ago

java 8, 11, 17 버전의 주요 특징에 대해서 설명해주세요.

java8

java11

java 17


스프링 프레임워크와 스프링 부트의 차이에 대해 설명해주세요.

  • spring boot 전용 dependency 제공 (여러 라이브러리를 통합하여 제공)
  • AutoConfiguration기능을 사용하여 Component Scan 대상인 클래스들을 Bean에 등록
  • 내장 WAS를 가지고 있기 때문에, war가 아닌 jar 파일만으로도 간편하게 배포 가능

Bean 생명주기에 대해 설명해주세요.

  1. 스프링 컨테이너 생성
  2. 스프링 Bean 생성
  3. 의존관계 주입
  4. 초기화 콜백 (@PostConstruct)
  5. Bean 사용
  6. 소멸 전 콜백 (@PreDestroy)
  7. 스프링 종료

스프링이 여러 요청을 처리하는 방법을 설명해주세요.

Spring MVC

  1. 클라이언트가 서버에 요청하면 DispatcherServlet이 요청을 가로챈다.
  2. HandlerMapping은 어떤 컨트롤러에게 가로챈 요청을 위임하면 좋을지 판단한다.
  3. 요청에 매핑된 Controller가 있다면 @RequestMapping을 통하여 요청을 처리할 메서드에 도달한다. (HandlerAdapter)
  4. Controller는 해당 요청을 처리할 Service에게 비즈니스 로직을 위임
  5. 결과물을 받은 Controller는 Model 객체에 결과물을 넣거나, View 정보를 담아 DispatcherServlet에게 보낸다.
  6. DispatcherServletViewResolver에게 뷰에 대한 정보를 넘긴다.
  7. ViewResolver는 해당 View를 찾아 DispatcherServlet에게 알려준다. (suffix, prefix를 붙이는 것도 여기서 수행)
  8. DispatcherServlet은 응답할 View에게 Render를 지시하고 View는 응답 로직을 처리한다.
  9. DispatcherServlet이 클라이언트에게 렌더링된 View를 응답한다.

Spring REST API

  1. 클라이언트가 서버에 요청하면 DispatcherServlet이 요청을 가로챈다.
  2. HandlerMapping은 어떤 컨트롤러에게 가로챈 요청을 위임하면 좋을지 판단한다.
  3. 요청에 매핑된 Controller가 있다면 @RequestMapping을 통하여 요청을 처리할 메서드에 도달한다. (HandlerAdapter)
  4. Controller는 해당 요청을 처리할 Service에게 비즈니스 로직을 위임
  5. 결과물을 받은 Controller는 ResponseEntity에 이를 담아 DispatcherServlet에게 보낸다.
  6. DispatcherServlet은 클라이언트에게 Response를 응답한다.
BaeYoungSuk commented 5 months ago

java 8, 11, 17 버전의 주요 특징에 대해서 설명해주세요.

스프링 프레임워크와 스프링 부트의 차이에 대해 설명해주세요.

스프링 부트는

  1. Tomcat이 내장되어 있다.
  2. starter를 통해 의존성을 관리할 수 있다.
  3. XML 설정을 하지 않아도 된다.
  4. Jar file을 이용해서 손쉽게 배포할 수 있다.
  5. 주로 REST-API 개발에 사용된다.

와 같은 특징이 있습니다.

Bean 생명주기에 대해 설명해주세요.

  1. 스프링 IoC 컨테이너 생성
  2. 스프링 빈 생성
  3. 의존관계 주입
  4. 초기화 콜백 메소드 호출
  5. 본연의 동작 수행
  6. 소멸 전 콜백 메소드 호출
  7. 스프링 종료

의 생명주기를 가집니다.

스프링이 여러 요청을 처리하는 방법을 설명해주세요

스프링 부트의 내장 톰캣은 다중 요청 처리를 위해 쓰레드풀을 생성합니다. 이후 유저 요청이 들어오면 쓰레드 풀에서 하나씩 쓰레드를 할당하고, 해당 쓰레드에서 Dispatcher Servlet을 거쳐 유저 요청을 처리합니다. 작업을 모두 수행하고 나면 쓰레드는 쓰레드풀로 반환됩니다.

rt3310 commented 5 months ago

java 8, 11, 17 버전의 주요 특징에 대해서 설명해주세요.

Java 8에는 대표적으로 람다 표현식, default 메서드, 옵셔널, 스트림 API 등이 추가되었습니다. Java 11에는 대표적으로 String 클래스에 여러 메서드가 추가되고 javac를 통해 컴파일하지 않고도 java 파일을 바로 실행할 수 있는 환경으로 변경되었습니다. Java 17에는 sealed class가 추가되었고 프리뷰 단계인 패턴 매칭과 인큐베이터 단계인 외부 함수 및 메모리 api가 있습니다.

스프링 프레임워크와 스프링 부트의 차이에 대해 설명해주세요.

스프링(Spring)은 프레임워크이며, 스프링 부트(Spring Boot)는 스프링 프레임워크를 기반으로 한 도구입니다. 스프링은 설정 파일을 작성해야 하지만, 스프링 부트는 자동 설정을 제공하여 간편하게 개발할 수 있습니다. 또한, 스프링 부트는 내장 서버를 제공하여 쉽게 웹 애플리케이션을 실행할 수 있습니다. Spring은 스프링 프레임워크를 보다 세밀하게 제어하고자 하는 경우에, Spring Boot는 빠르고 간단하게 스프링 애플리케이션을 개발하고자 하는 경우에 사용됩니다.

Bean 생명주기에 대해 설명해주세요.

스프링 내부에서는 스프링의 라이프 사이클과 관련하여 이벤트가 있는데 순서는 다음과 같습니다. 스프링 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 → 앱 동작 수행 → 소멸 전 콜백 → 스프링 종료

스프링이 여러 요청을 처리하는 방법을 설명해주세요

스프링부트는 내장 서블릿 컨테이너인 Tomcat을 이용합니다. Tomcat은 다중 요청을 처리하기 위해서, 부팅할 때 스레드의 컬렉션인 Thread Pool을 생성합니다. 이후 유저 요청(HttpServletRequest)가 들어오면 Thread Pool에서 하나씩 Thread를 할당합니다. 해당 Thread에서 스프링부트에서 작성한 Dispatcher Servlet을 거쳐 유저 요청을 처리합니다. 작업을 모두 수행하고 나면 스레드는 스레드 풀로 반환됩니다.

rt3310 commented 5 months ago
server:
  tomcat:
    threads:
      max: 200 # 생성할 수 있는 thread의 총 개수
      min-spare: 10 # 항상 활성화 되어 있는 thread의 개수
    max-connections: 8192 # 수립 가능한 connection의 총 개수
    accept-count: 100 # 작업 큐의 사이즈
    connection-timeout: 20000 # timeout 판단 기준 시간, 20초
  port: 8080 # 서버를 띄울 포트번호
rt3310 commented 5 months ago
  1. 첫 작업이 들어오면, core size 만큼의 스레드를 생성한다.
  2. 유저 요청이 들어올 때마다 작업 큐에 담아둔다.
  3. core size의 스레드 중, 유휴상태(idle)인 스레드가 있다면 작업 큐에서 작업을 꺼내 스레드에 작업을 할당하여 작업을 처리한다.
    1. 만약 유휴상태인 스레드가 없다면, 작업은 작업 큐에서 대기한다.
    2. 그 상태가 지속되어 작업 큐가 꽉 찬다면, 스레드를 새로 생성한다.
    3. 3번 과정을 반복하다 스레드 최대 사이즈에 도달하고 작업 큐도 꽉 차게 되면, 추가 요청에 대해선 connection-refused 오류를 반환한다.
  4. task가 완료되면 스레드는 다시 유휴상태로 돌아간다.
    1. 작업 큐가 비어 있고 core size 이상의 스레드가 생성되어 있다면 스레드를 destroy 한다.
rt3310 commented 5 months ago

스레드를 많이 띄워야 하는 상황

적정 스레드 풀 개수

병렬 Work 스레드의 수(poolSize) 지연 시간(ms) 처리량(TPS) 관계

rt3310 commented 5 months ago

스레드풀 테스트

지금까지 알아본 바에 의하면, 유저 요청이 들어올 때(Connection)마다 스레드가 하나씩 할당될 것이고, 작업 큐가 가득 차면 스레드가 늘어날 것이고, 스레드도 가득 차면 유저 요청이 거절될 것이다.

테스트로 스프링 프로젝트를 하나 만드록, application.yml에 다음과 같이 옵션을 주었다.

server:
  tomcat:
    threads:
      max: 2
      min-spare: 2
    accept-count: 1
  port: 5000

그 후 3초를 대기하는 api를 하나 만들었다.

@Controller
public class HelloController {

    private Logger log = LoggerFactory.getLogger(HelloController.class);

    @RequestMapping("/hello")
    public ResponseEntity<Void> hello() throws InterruptedException {
        log.info("start");
        Thread.sleep(3000);
        log.info("end");
        return ResponseEntity.ok().build();
    }
}

이 프로젝트가 감당할 수 있는 요청은 동원할 수 있는 스레드 2개, 그리고 작업 큐 1개에서 대기할 요청까지 최대 3개이다.

이를 확인하기 위해 또 다른 스프링 프로젝트를 만들어, 요청을 5번 보내기로 했다.

@Controller
public class PowerSmashController {

    private Logger log = LoggerFactory.getLogger(PowerSmashController.class);

    @GetMapping("/")
    @ResponseBody
    public ResponseEntity<Void> 요청5개쏘는메소드() {
        RestTmeplate restTemplate = new RestTemplate();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                log.info("발사!");
                String result = restTemplate
                    .getForObject("http://localhost:5000/hello", String.class);
                log.info(result);
            });
            thread.start();
        }
        return ResponseEntity.ok().build();
    }
}

해당 코드는 밀리세컨드 단위로 5번의 요청을 한번에 보내게 된다. 2개의 요청이 2개의 활성 스레드에서 각각 3초동안 block되고, 3번째 요청은 작업 큐에서 대기할 것이므로 4, 5번째 요청은 거절되어야 한다.

Untitled

요청은 동시에 갔지만 응답은 3초 단위로 텀을 두고 순차적으로 처리되는 모습을 볼 수 있다.

Untitled2

서버 측도 확인해보면

2개의 활성 스레드가 차근차근, 3초 간격으로 작업을 처리한 걸 볼 수 있다. 작업 큐는 1칸 이므로 2개의 4, 5번째 요청은 받을 수 없었을텐데 어떻게 가능했을까?

rt3310 commented 5 months ago

자바는 어디서 느릴까?

일단 자바는 C/C++과 달리 직접 메모리를 관리하고 OS 레벨의 시스템 콜을 직접 사용하기는 어렵다. JNI를 사용하는 것은 여기서는 배제하도록 하자. 자바는 JVM 위에서 동작하므로 주로 C/C++에 비하면 느리다고 인식되고 실제로도 그렇다.

자바가 특별히 성능이 좋지 않은 부분은 IO다. IO 성능 문제를 개선하는 것이 바로 java.nio 패키지이다.

자바 NIO

자바 4부터 새로운 입출력(NIO: New Input/Output)이라는 뜻에서 java.nio 패키지가 포함되었는데, 자바 7부터 자바 IO와 자바 NIO 사이의 일관성 없는 클래스 설계를 바로 잡고, 비동기 채널 등의 네트워크 지원을 대폭 강화한 NIO.2 API가 추가되었다. NIO.2는 java.nio2 패키지로 제공되지 않고 기존 java.nio의 하위 패키지 java.nio.channels, java.nio.charset, java.nio.file에 통합되어 있다.

자바 IO와 NIO 차이

구분 | IO | NIO -- | -- | -- 입출력 방식 | 스트림 방식 | 채널 방식 버퍼 방식 | 논버퍼 | 버퍼 비동기 방식 | 지원 안함 | 지원 블로킹 / 논블로킹 방식 | 블로킹 방식만 지원 | 블로킹/논블로킹 방식 모두 지원

스트림 vs 채널

IO 는 스트림 기반이다. 스트림은 입력 스트림과 출력 스트림으로 구분되어 있기 때문에 데이터를 읽기 위해서는 입력 스트림을 생성해야 하고, 데이터를 출력하기 위해서는 출력 스트림을 생성해야 한다. NIO는 채널 기반. 채널은 스트림과 달리 양방향으로 입력과 출력이 가능하다. 그렇기 때문에 입력과 출력을 위한 별도의 채널을 만들 필요가 없다.

논버퍼 vs 버퍼

IO에서는 출력 스트림이 1바이트를 쓰면 입력 스트림이 1바이트를 읽는다. 이것보다는 버퍼를 사용해서 복수 개의 바이트를 한꺼번에 입력받고 출력하는 것이 빠른 성능을 낸다. 그래서 IO는 버퍼를 제공해주는 보조 스트림인 BufferedInputStream, BufferedOutputStream을 연결해서 사용하기도 한다. NIO는 기본적으로 버퍼를 사용해서 입출력을 하므로 IO 보다는 성능이 좋다. 채널은 버퍼에 저장된 데이터를 출력하고, 입력된 데이터를 버퍼에 저장한다.

또한 IO는 스트림에서 읽은 데이터를 즉시 처리하기 때문에 입력된 전체 데이터를 별도로 저장하지 않으면, 입력된 데이터의 위치를 자유롭게 이용할 수 없다. NIO는 읽은 데이터를 무조건 버퍼에 저장하기 때문에 버퍼 내에서 데이터의 위치를 이동해 가면서 필요한 부분만 읽고 쓸 수 있다.

블로킹 vs 논블로킹

IO는 블로킹(blocking)이 된다. 입력 스트림의 read() 출력 스트림의 write() 메소드를 호출하면 블로킹 된다. IO 스레드가 블로킹되면 다른 일을 할 수 없고 블로킹을 빠져나오기 위해 인터럽트도 할 수 없다. 블로킹을 빠져나오는 방법은 스트림을 닫는 것이다.

NIO는 블로킹와 논블로킹 특징을 모두 가지고 있다. IO 블로킹과의 차이점은 NIO 블로킹은 스레드를 인터럽트 함으로써 빠져나올 수가 있다는 것이다. NIO의 논블로킹은 입출력 작업 준비가 완료된 채널만 선택해서 작업 스레드가 처리하기 때문에 작업 스레드가 블로킹 되지 않는다. NIO 논블로킹의 핵심 객체는 멀티플렉서인 Selector이다. 셀렉터는 복수 개의 채널 중에서 이벤트가 준비 완료된 채널을 선택하는 방법을 제공해준다.

자바 NIO의 주요 키워드

Channel

소켓을 통해 non-blocking read를 할 수 있도록 지원하는 Connection.

읽기, 쓰기 하나씩 쓸 수 있는 스트림은 단방향식, 채널은 읽기 쓰기 둘 다 가능한 양방향식 입출력 클래스이며 네이티브 IO, Scatter/Gather 구현으로 효율적인 IO 처리 (시스템 콜 수 줄이기, 모아서 처리하기)

Buffer (NIO에서 제공하는 Buffer 클래스)

커널에 의해 관리되는 시스템 메모리를 직접 사용할 수 있는 채널에 의해 직접 read 되거나 write 될 수 있는 배열과 같은 객체.

Selector

네트워크 프로그래밍의 효율을 높이기 위한 것

클라이언트 하나 당 스레드 하나를 생성해서 처리하기 때문에 스레드가 많이 생성될수록 급격한 성능 저하를 가졌던 단점을 개선하는 Reactor 패턴의 구현체

Selector는 어느 channel set이 IO event를 가지고 있는지를 알려준다. Selector.select()는 I/O 이벤트가 발생한 채널 set을 return 한다. return할 channel이 없다면 계속 block 된다. 이 block 된 것을 바로 return 시켜주는 것이 Selector.wakeup()이다.

Selector.selectedKeys()는 Selection Key를 return 해 준다. Reactor는 이 Selection Key를 보고 어떤 handler로 넘겨줄지를 결정한다.

Selection Key

Selector와 Channel 간의 관계를 표현해주는 객체이다. Selector가 제공한 Selection Key를 이용해 Reactor는 채널에서 발생하는 I/O 이벤트로 수행할 작업을 선택할 수 있다. ServerSocketChannel에 selector를 등록하면 key를 준다. 이 key가 SelectionKey이다.

Charset

캐릭터셋을 나타낸다. 바이트 데이터와 문자 데이터를 인코딩/디코딩 할 때 사용된다.

IO와 NIO의 선택

NIO는 다수의 연결이나 파일들을 논블로킹이나 비동기 처리할 수 있어서 많은 스레드 생성을 피하고 스레드를 효과적으로 재사용한다는 장점이 있다. 그래서 NIo는 연결 수가 많고 하나의 입출력 처리 작업이 오래 걸리지 않는 경우에 사용하는 것이 좋을 것이다. 스레드에서 입출력 처리가 오래 걸린다면 대기하는 작업의 수가 늘어나게 되므로 장점이 사라진다.

많은 데이터 처리의 경우 IO가 좋을 수 있다. NIO는 버퍼 할당 크기가 문제가 되고, 모든 입출력 작업에 버퍼를 무조건 사용해야 하므로 즉시 처리하는 IO보다 성능 저하가 있을 수 있다. 연결 클라이언트 수가 적고 전송되는 데이터가 대용량이면서 순차적으로 처리될 필요성이 있는 경우 IO로 구현하는 것이 좋은 선택일 수 있다.

rt3310 commented 5 months ago

BIO Connector & NIO Connector

비밀은 Connector에 있다. 위 얘기는 BIO(Blocking I/O) Connector일 때 유효한 얘기이다. 그러나 톰캣 8.0부터 NIO(Nonblockig I/O) Connector가 기본으로 채택되고, 9.0부터는 BIO Connector가 deprecate 됨으로써 다른 방식으로 진행되게 된다.

Connector

Connector는 소켓 연결을 수입하고 데이터 패킷을 획득하여 HttpServletRequest 객체로 변환하고 Servlet 객체에 전달하는 역할을 한다.

  1. Acceptor에서 while 문으로 대기하며 port listen을 통해 Socket Connection을 얻게 된다.
  2. Socket Connection으로부터 데이터를 획득한다. 데이터 패킷을 파싱해서 HttpServletRequest 객체를 생성한다.
  3. Servlet Container에 해당 요청 객체를 전달한다. ServletContainer는 알맞은 서블릿을 찾아 요청을 처리한다.

BIO Connector

BIO Connector는 Socket Connection을 처리할 때 Java의 기본적인 I/O 기술을 사용한다.

thread pool에 의해 관리되는 thread는 소켓 연결을 받고 요청을 처리하고 요청에 대해 응답한 후 소켓 연결이 종료되면 pool에 다시 돌아오게 된다.

즉, connection이 닫힐 때까지 하나의 thread는 특정 connection에 계속 할당되어 있을 것이다.

이러한 방식으로 thread를 할당하여 사용할 경우, 동시에 사용되는 thread 수가 동시 접속할 수 있는 사용자의 수가 될 것이다. 그리고 이러한 방식을 채택해서 사용할 경우 thread들이 충분히 사용되지 않고 idle 상태로 낭비되는 시간이 많이 발생한다. 이러한 문제점을 해결하고 리소스를 효율적으로 사용하기 위해 NIO Connector가 등장했다.

NIO Connector

NIO Connector는 I/O가 아니라 Http11NioProtocol을 사용한다.

NIO Connector에선 Poller라고 하는 별도의 스레드가 커넥션을 처리한다. Poller는 Socket들을 캐시로 들고 있다가 해당 Socket에서 data에 대한 처리가 가능한 순간에만 thread를 할당하는 방식을 사용해서 thread가 idle 상태로 낭비되는 시간을 줄여준다.

Untitled

Acceptor는 이름 그대로 Socket Connection을 accept한다. serverSocket.accept() 방식을 사용하고 있다. 소켓에서 Socket Channel 객체를 얻어서 톰캣의 NioChannel 객체로 변환한다. 그리고 추가로 NioChannel 객체를 PollerEvent라는 객체로 한 번 더 캡슐화해서 event queue에 넣게 된다. Acceptor는 Event Queue의 공급자, Poller thread는 Event Queue의 사용자이다.

Untitled2

Poller는 NIO의 Selector를 가지고 있다. Selector에는 다수의 채널이 등록되어 있고, select 동작을 수행하여 데이터를 읽을 수 있는 소켓을 얻는다. 그리고 Worker Thread Pool에서 이용할 수 있는 Worker Thread를 얻어서 해당 소켓을 worker thread에 넘긴다.

Java Nio Selector를 사용해서 data 처리가 가능할 때만 Thread를 사용하기 때문에 idle 상태로 낭비되는 Thread가 줄어들게 된다.

Poller에선 Max Connection까지 연결을 수락하고, 셀렉터를 통해 채널을 관리하므로 작업 큐 사이즈와 관계 없이 추가로 커넥션을 refuse하지 않고 받아놓을 수 있다.

스레드 또한 모자라다면 max 사이즈까지 스레드를 추가하는 것을 볼 수 있다.