2026년 02월 23일

🧵 스레드(Thread) 완전 정복

Java Spring Boot
Cover Image

🧵 스레드(Thread) 완전 정복

"프로세스가 공장이라면, 스레드는 그 안에서 일하는 작업자다."


🔵 스레드란?

스레드(Thread) = 프로세스 내에서 실행되는 독립적인 흐름의 단위

OS가 CPU 스케줄링을 통해 실행 시간을 할당하는 가장 작은 단위다.

시대방식특징
과거단일 스레드 (Single-threaded)프로세스 하나 = 작업 하나
현재멀티 스레드 (Multi-threaded)프로세스 하나 = 작업 여러 개 동시 처리

🏗️ 공유 vs 독립 — 핵심 구조

스레드를 이해하는 가장 중요한 질문은 "무엇을 공유하고, 무엇을 따로 갖는가?" 다.

📦 공유하는 것 (Shared)

같은 프로세스의 스레드들은 아래 영역을 함께 사용한다.

영역내용
Code실행할 프로그램 코드
Data전역 변수, static 변수
Heapnew로 생성한 객체, 동적 할당 메모리

🔒 독립적으로 가지는 것 (Private)

각 스레드가 자신만의 실행 흐름을 유지하기 위해 반드시 따로 가져야 하는 것들이다.

항목역할
Stack함수 호출 시 지역 변수 · 리턴 주소 저장
PC (Program Counter)현재 실행 중인 코드 위치 추적
Register Set계산 중인 중간 값 보관

💡 Stack이 독립적이기 때문에 각 스레드는 서로 다른 함수를 동시에 호출해도 충돌 없이 작동한다.


✅ 스레드를 사용하는 이유

1. 자원 효율성

프로세스 생성은 메모리 전체를 새로 할당하는 무거운 작업이다. 반면 스레드는 Code/Data/Heap을 공유하므로 Stack과 PC만 추가하면 된다. 그래서 스레드를 Lightweight Process(경량 프로세스) 라고 부르기도 한다.

2. 응답성 (Responsiveness)

웹 브라우저에서 파일을 다운로드하면서 동시에 다른 탭을 탐색할 수 있는 이유가 바로 멀티스레딩 덕분이다.

Thread A → 파일 다운로드 중...
Thread B → UI 렌더링 처리 중...
Thread C → 백그라운드 캐시 갱신 중...

3. 데이터 공유의 용이성

프로세스 간 통신(IPC)은 복잡하고 느리지만, 스레드는 Heap을 공유하므로 데이터 교환이 훨씬 빠르고 간단하다.

| | 프로세스 간 통신 (IPC) | 스레드 간 통신 | |--|------------------------|----------------| | 방식 | 소켓, 파이프, 공유 메모리 등 | Heap 직접 공유 | | 속도 | 느림 | 빠름 | | 복잡도 | 높음 | 낮음 |


⚔️ 양날의 검 — 동기화 문제

공유는 편리하지만, 동시에 접근할 때 충돌이 발생한다.

🚨 Race Condition (경쟁 상태)

// 두 스레드가 동시에 count++ 실행 시
int count = 0;

Thread A: count 읽기 (0) → +1 → 저장 (1)
Thread B: count 읽기 (0) → +1 → 저장 (1)  ← A의 결과 덮어씀!

// 기대: count = 2
// 실제: count = 1  ← 데이터 손상!

🔑 Java의 동기화 해결책

1. synchronized 키워드 — 메서드나 블록에 락(Lock)을 걸어 하나의 스레드만 접근 허용

public synchronized void increment() {
    count++;  // 한 번에 하나의 스레드만 실행
}

2. ReentrantLock — 더 세밀한 락 제어가 필요할 때

Lock lock = new ReentrantLock();

lock.lock();
try {
    count++;
} finally {
    lock.unlock();  // 반드시 해제
}

3. AtomicInteger — 단순 숫자 연산에 최적화된 원자적(Atomic) 변수

AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();  // 스레드 안전한 +1

☠️ Deadlock (교착 상태)

두 스레드가 서로 상대방의 락이 풀리길 기다리다 영원히 멈추는 상태.

Thread A: 락 A 보유 → 락 B 기다리는 중...
Thread B: 락 B 보유 → 락 A 기다리는 중...
→ 둘 다 영원히 대기 → 시스템 멈춤 💀

⚠️ Deadlock 예방을 위해 락 획득 순서를 일관되게 유지하거나 타임아웃을 설정해야 한다.


☕ Java에서 스레드 사용하기

Thread 클래스 상속

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread 실행: " + Thread.currentThread().getName());
    }
}

MyThread t = new MyThread();
t.start();  // run()을 별도 스레드에서 실행

Runnable 인터페이스 구현 (권장)

Runnable task = () -> {
    System.out.println("Thread 실행: " + Thread.currentThread().getName());
};

Thread t = new Thread(task);
t.start();

ExecutorService — 스레드 풀 (실무 권장)

ExecutorService executor = Executors.newFixedThreadPool(4); // 스레드 4개

executor.submit(() -> System.out.println("작업 1 실행"));
executor.submit(() -> System.out.println("작업 2 실행"));

executor.shutdown(); // 작업 완료 후 종료

💡 실무에서는 스레드를 직접 생성하기보다 ExecutorService(스레드 풀) 를 사용하는 것이 자원 관리 측면에서 훨씬 안전하고 효율적이다.


📝 핵심 요약

항목내용
스레드 정의프로세스 내 독립적인 실행 흐름의 단위
공유 영역Code, Data, Heap
독립 영역Stack, PC, Register
장점자원 효율, 응답성, 빠른 데이터 공유
위험Race Condition, Deadlock
Java 동기화synchronized, ReentrantLock, AtomicInteger
실무 권장ExecutorService(스레드 풀) 사용
← 목록으로 돌아가기