대학교 운영체제 수업을 듣던 도중 쓰레드 부분이 나와 자바에서는 이를 어떻게 처리하는지와 멀티쓰레딩을 어떻게 구현하는지 궁금해졌습니다.
이에 쓰레드의 개념부터 자바에서의 멀티쓰레딩 수행방식까지 차례로 알아보려합니다.
- 쓰레드 동작 원리
- 멀티쓰레드 동작 원리
- 시스템 콜 사용
- 자바의 쓰레드 동작 방식
- Thread.start()
- Spring MVC에서의 쓰레드
쓰레드는 프로세스 안에 존재하는 일련의 작업 단위입니다.
다음은 인간의 계산식 인식 및 처리 과정입니다.
동시에 3이라는 답을 도출할 수 있어 마치 동시에
처리하는 것 처럼 느껴지지만, 사실 인간도 위 1+2 식을 먼저 읽고 매우 빠르게
연산하여 답을 도출한 후 밑의 2+1 식을 읽어서 처리하는 단계로 이루어집니다.
즉, 한 타임에 하나의 연산식만을 읽어 처리할 수 있습니다.
이를 CPU의 쓰레드 처리에 대입해보면,
인간의 연산 처리와 동일한 방식으로 T1 작업을 먼저 읽고 매우 빠르게
수행하여 작업을 끝마친 후 T2 작업을 읽어서 처리합니다.
기본적으로 작업을 처리해주는 CPU는 인간의 뇌와 같이 한 타임에 하나의 명령어만 처리할 수 있기 때문입니다.
프로세스 혹은 쓰레드 작업 단위를 동시에 처리하지 않습니다.
명령어를 순차적으로 읽어서 처리하는데 ms 단위로 매우 빠르게 처리하여
사람이 인식할 때 동시에 처리되는 것처럼 보이는 것입니다.
운영체제는 위의 쓰레드 작업 처리 원리와 더불어
더욱 더 동시에 처리되는 것 처럼 보이게 하기 위해(혹은 CPU 자원 사용의 효율성을 높이기 위해) 다음과 같이 스케쥴링 방식
을 사용합니다.
자세한 동작 방식은 운영체제 시간의 CPU 스케쥴링을 참고해주세요.
이처럼 CPU는 단지 오는대로 처리할 뿐 동시에 실행되진 않지만,
이렇게 스케쥴링을 거쳐서 정말 동시에 처리되는 것 처럼 보이게 합니다.
쓰레드를 만들기 위해서는 다음과 같이 운영체제의 커널 자원을 사용해야 합니다.
이를 사용자 모드에서 수행하기 위해서는 System call
이 필요합니다.
콘솔에 출력하는 메서드를 예로 들면,
다음과 같이 컴파일되어 기계어로 되면 CPU는 이를 읽어 명령을 수행합니다. 콘솔에 출력하기 위해서는 모니터라는 하드웨어 자원과 원만한 합의가 이루어져야 하는데(I/O) 이를 위해 System call을 하여 운영체제에게 출력해달라고 맡겨야 합니다.
쓰레드도 마찬가지로, 운영체제에게 쓰레드 생성을 맡겨 관리할 수 있도록 구현되어 있습니다.
자바에서는 JNI를 이용하여 운영체제에게 호출합니다. 이는 C++ 코드로 구현되어 있으며 JVM이 JNI 코드를 실행하여 C++ 코드가 실행되는 이중 구조로 이루어져 있습니다.
자세한 방식은 코드 분석과정에서 설명하겠습니다.
쓰레드에는 다음과 같이 User-level Thread와 Kernel-level Thread로 나뉩니다. 이는 누가 통제해서 Thread를 관리하는가를 기준으로 나누어집니다.
자세한 방식은 운영체제 쓰레드 부분을 참고해주세요.
자바에서는 1:1 매핑 방식을 사용하고 있습니다.
Java 어플리케이션에서 쓰레드를 생성하면 Kernel 모드에서 생성되어 작업을 1:1로 매핑시켜줍니다.
이렇게 매핑시켜서 한개의 작업마다 운영체제의 작업 스케쥴링 등 관리 항목에 포함되도록 설계합니다.
1:1 매핑 방식을 사용하는 이유는 여러가지가 있겠지만, 가장 큰 이유는 자바의 슬로건이자 가장 큰 장점인 이식성
에 있습니다.
여러 운영체제에서 각기 다른 쓰레드 설계를 가지고 있겠지만, 1:1로 구현할 시 다른 운영체제에서도 해당 코드(어플리케이션)를 변경하지 않고 그대로 적용시킬 수 있습니다.
Java MT라는 것도 있지만, 이는 Solaris 운영체제 한정이며, 제 레벨에서는 일반적으로 잘 사용하지 않으므로 이는 설명에서 제외하겠습니다..
그리고 특히 자바에서는 매핑을 끝마친 하나의 쓰레드가 생성되면, 작업이 끝나고 삭제하는것이 아닌, 재할당하는 방식으로 사용됩니다.
Spring MVC는 기본적으로 Servlet
기술을 기반으로 동작합니다.
서블릿은 멀티쓰레드 환경에서 동작하기 때문에, Spring MVC 역시 멀티쓰레드를 활용하여 요청 처리를 합니다.
보통 Spring MVC는 요청당 새로운 쓰레드를 생성하여 요청 처리를 수행합니다. 이를 통해 여러 개의 요청을 동시에 처리할 수 있습니다. 또한, 각 요청은 별도의 쓰레드에서 처리되기 때문에, 다른 요청에 영향을 주지 않습니다.
Spring MVC에서는 Thread pool을 사용하여 쓰레드를 관리합니다. 쓰레드풀은 미리 일정 개수의 쓰레드를 생성해두고, 요청이 들어올 때마다 쓰레드를 할당하여 요청을 처리하는 방식입니다. 이를 통해 쓰레드를 생성하고 삭제하는 데 드는 오버헤드를 줄일 수 있습니다.
How Java thread maps to OS thread?
Java on Solaris 7 Developer's Guide - Chapter 2 Multithreading
시스템 콜과 자바에서의 시스템 콜 사용례
[JAVA] 다중 작업(멀티 스레딩)을 위한 Thread 기초 개념