1. 왜 멀티스레드가 필요해졌을까?
지난번 작성한 단일 소켓 서버 코드를 실행한 뒤, 두 개의 클라이언트를 띄워 서버에 접속해 보았습니다. 결과는 예상대로 '블로킹(Blocking)'이었습니다. 첫 번째 클라이언트가 서버와 연결되어 통신하는 동안, 두 번째 클라이언트는 접속은 되었으나 메시지를 보내도 서버가 전혀 받지 못하는 대기열 적체 현상을 직접 눈으로 확인했습니다.
수많은 요청을 동시에 처리해야 하는 웹 서버(WAS)가 한 번에 한 명의 손님만 응대할 수는 없습니다. 이 문제를 구조적으로 해결하기 위해 '멀티스레딩(Multi-Threading)' 방식을 도입해야 합니다.
2. 프로세스(Process)와 스레드(Thread)의 차이
멀티스레드를 코드로 구현하기 전에, 운영체제 관점에서의 기본 CS 지식을 짚고 넘어가겠습니다.
- 멀티프로세스: 운영체제로부터 각각 독립된 메모리 영역을 할당받아 여러 프로그램을 동시에 실행하는 것입니다. 독립적이므로 안전하지만, 서로 통신(IPC)하기 까다롭고 메모리 차지 비중이 큽니다.
- 멀티스레드: 하나의 프로세스 내에서 여러 개의 실행 흐름(스레드)을 동시에 돌리는 것입니다. 스레드들은 프로세스의 자원(메모리의 Heap 영역 등)을 서로 공유하기 때문에 생성 비용이 적고 통신이 빠르다는 압도적인 장점이 있습니다.
3. 동시성(Concurrency) vs 병렬성(Parallelism)
멀티스레딩의 특징을 설명할 때 반드시 등장하는 두 가지 핵심 개념입니다.

사진 출처 : https://connie.tistory.com/12
하나의 CPU 코어가 여러 스레드를 번갈아 가며 실행하는 성질입니다. 워낙 빠르게 번갈아 실행(Context Switching)하기 때문에 사람의 눈에는 마치 동시에 실행되는 것처럼 보일 뿐, 실제로는 한순간에 하나의 작업만 처리합니다. (논리적 동시성)
🚀 병렬성 (Parallelism): 멀티 코어의 힘
멀티 코어 환경에서 여러 개의 CPU 코어가 각각의 스레드를 맡아 물리적으로 완벽하게 동시에 실행하는 성질입니다.
4. 자바(Java)에서의 멀티스레딩 구현 방법 2가지
자바에서 새로운 스레드를 생성하여 작업을 맡기는 방법은 크게 두 가지가 있습니다.
① Thread 클래스 상속받기
Thread 클래스를 상속받은 뒤, run() 메서드를 반드시 오버라이딩하여 스레드가 실행할 코드를 작성합니다. (만약 오버라이딩하지 않으면, 기본 Thread 클래스의 run()은 아무 작업도 수행하지 않고 종료됩니다.)
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread 클래스를 상속받은 스레드 실행!");
}
}
// 실행 방법
MyThread thread = new MyThread();
thread.start();
② Runnable 인터페이스 구현하기 (권장)
실행할 작업 내용만 Runnable 인터페이스의 run() 메서드에 규정해 두고, 이를 Thread 객체에 생성자로 던져주는 방식입니다.
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable 인터페이스를 구현한 스레드 실행!");
}
}
// 실행 방법
Thread thread = new Thread(new MyRunnable());
thread.start();
5. 실무에서는 왜 Runnable을 더 많이 사용할까?
대부분의 실무 환경이나 프레임워크에서는 Thread 상속보다 Runnable 인터페이스 구현을 압도적으로 선호합니다. 그 이유는 다음과 같은 객체지향적 설계의 이점 때문입니다.
1. 다중 상속의 한계 극복: 자바는 클래스의 다중 상속을 허용하지 않습니다.
Thread 클래스를 상속받아버리면, 정작 내 비즈니스 로직에 필요한 다른 중요한 클래스를 상속받을 수 없게 됩니다. 인터페이스인 Runnable을 사용하면 확장에 훨씬 유연해집니다.2. 역할의 분리(SRP):
Thread 클래스는 '스레드라는 실행 흐름(기능)' 그 자체입니다. 반면 Runnable은 '실행되어야 할 작업(Task)'만을 정의합니다. 작업을 수행하는 주체(Thread)와 실제 작업 내용(Runnable)을 분리하는 것이 객체지향 설계에 더 부합합니다.다음 편에서는 이 Runnable 인터페이스를 활용하여, 서버와 클라이언트가 자유롭게 대화를 주고받는 완벽한 양방향 다중 채팅을 구현하며 겪었던 삽질의 기록을 정리해 보겠습니다.
'정리 > WAS' 카테고리의 다른 글
| 순수 Java로 WAS 구현 (6) - 프레임워크의 마법, 리플렉션(Reflection) 완벽 이해 (0) | 2026.05.15 |
|---|---|
| 순수 Java로 WAS 구현 (5) - HTTP 요청 파서(Parser) 구현과 3가지 트러블슈팅 (0) | 2026.05.15 |
| 순수 Java로 WAS 구현 (4) - 파싱(Parsing)을 위한 HTTP 메시지 구조 완벽 분석 (1) | 2026.05.14 |
| 순수 Java로 WAS 구현 (3) - 완벽한 양방향 통신 구현과 치명적인 삽질의 기록 (0) | 2026.05.13 |
| 순수 Java로 WAS 구현 (1) - WAS의 심장, 소켓(Socket) 프로그래밍 (0) | 2026.05.12 |