자바 멀티스레딩 마스터: 효율적인 동시성 프로그래밍 가이드
자바는 멀티스레드 프로그래밍 언어로, 하나의 프로그램 내에서 여러 작업을 동시에 수행할 수 있습니다. 멀티스레딩을 통해 CPU 코어를 최대한 활용하고 애플리케이션의 반응성과 처리량을 높일 수 있습니다.
스레드 생명 주기
스레드는 생성부터 종료까지 다음과 같은 상태를 거칩니다.
New (생성): new Thread()로 생성되었으나 start()가 호출되지 않은 상태
Runnable (실행 가능): start() 호출 후 스케줄러의 선택을 기다리는 상태
Running (실행 중): CPU를 할당받아 run() 메서드 실행 중
Waiting/Blocked (대기/차단): wait(), sleep(), I/O 대기, 락 획득 대기 등으로 일시 정지
Timed Waiting (시간 제한 대기): sleep(ms), wait(ms), join(ms) 등으로 정해진 시간 동안 대기
Terminated (종료): run() 메서드 완료 또는 예외로 종료
스레드 우선순위
MIN_PRIORITY = 1, NORM_PRIORITY = 5 (기본값), MAX_PRIORITY = 10
우선순위는 스케줄러의 힌트일 뿐, 실행 순서를 보장하지 않음 (OS 종속적)
스레드 생성 방법 1: Runnable 인터페이스 구현
상속의 제약 없이 스레드 로직을 분리할 수 있어 권장되는 방식입니다.
단계별 구현
Runnable 인터페이스 구현 및 run() 메서드 오버라이드
Thread(Runnable target, String name) 생성자로 스레드 객체 생성
start() 호출로 스레드 시작
예제
class TaskRunner implements Runnable {
private final String name;
public TaskRunner(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 3; i >= 0; i--) {
System.out.println(name + ": " + i);
try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
new Thread(new TaskRunner("스레드-A")).start();
new Thread(new TaskRunner("스레드-B")).start();
}
}
스레드 생성 방법 2: Thread 클래스 상속
Thread를 상속받아 run()을 오버라이드하는 방식입니다. 간단하지만 상속 제약이 있습니다.
예제
class WorkerThread extends Thread {
private final String name;
public WorkerThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 3; i >= 0; i--) {
System.out.println(name + ": " + i);
try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
new WorkerThread("워커-1").start();
new WorkerThread("워커-2").start();
}
}
주요 Thread 메서드
| 메서드 | 설명 |
start() | 새 스레드 시작 (run() 호출) |
run() | 스레드 실행 로직 (직접 호출 금지) |
sleep(long ms) | 현재 스레드 일시 정지 (static) |
join() / join(long ms) | 해당 스레드 종료까지 대기 |
interrupt() | 대기 중인 스레드 깨우기 |
yield() | 현재 스레드 양보 (static) |
setDaemon(true) | 데몬 스레드로 설정 (JVM 종료 시 자동 종료) |
isAlive() | 스레드 실행 중 여부 확인 |
currentThread() | 현재 실행 중인 스레드 참조 반환 (static) |
심화 주제: 동시성 제어
실무에서는 다음 개념들을 반드시 이해해야 합니다.
스레드 동기화: synchronized, Lock, ReentrantLock으로 공유 자원 보호
스레드 간 통신: wait(), notify(), notifyAll() 또는 Condition 활용
데드락 방지: 락 순서 고정, 타임아웃 설정, tryLock() 활용
Executor 프레임워크: ExecutorService, 스레드 풀, Future, CompletableFuture로 고수준 동시성 제어
동시성 컬렉션: ConcurrentHashMap, CopyOnWriteArrayList, BlockingQueue 등
모범 사례
Runnable 구현 우선 고려 (함수형 인터페이스로 람다 활용 가능)
스레드 풀(Executors) 사용하여 스레드 생성/소멸 비용 절감
공유 가변 상태 최소화, 불변 객체 활용
volatile 키워드로 가시성 보장, Atomic 클래스로 원자성 확보
예외 처리 누락 방지: 스레드 내 예외는 메인 스레드로 전파되지 않음