자바는 멀티스레드(Multi-thread) 환경을 강력하게 지원하여 여러 작업을 병렬로 처리할 수 있게 한다.
기본적인 스레드 생성부터 현대적인 스레드 풀 관리 방식까지 정리한다.
스레드 생성: Runnable 인터페이스
가장 권장되는 방식은 Runnable 인터페이스를 구현하는 것이다.
public class ThreadBasic {
public static void main(String[] args) {
// 람다식을 이용한 작업 정의
Runnable task = () -> {
String name = Thread.currentThread().getName();
System.out.println(name + " : 작업 시작");
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println(name + " : 작업 완료");
};
Thread t1 = new Thread(task, "Worker-1");
Thread t2 = new Thread(task, "Worker-2");
t1.start();
t2.start();
}
}
동기화 (Synchronization)
여러 스레드가 공유 자원에 동시에 접근할 때 발생하는 데이터 오염을 방지하기 위해 synchronized 키워드를 사용한다.
class Counter {
private int count = 0;
// 임계 영역(Critical Section) 설정
public synchronized void increment() {
count++;
}
public int getCount() { return count; }
}
스레드 풀 (ExecutorService)
스레드를 직접 생성하고 관리하는 대신, 스레드 풀을 사용하여 자원을 효율적으로 관리한다.
import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) throws Exception {
// 4개의 스레드를 가진 고정 스레드 풀 생성
ExecutorService executor = Executors.newFixedThreadPool(4);
// 결과값을 반환하는 작업 (Callable)
Callable<Integer> task = () -> {
Thread.sleep(500);
return 42;
};
// 작업 제출 및 Future 객체 획득
Future<Integer> future = executor.submit(task);
// 결과 대기 및 출력
System.out.println("Result: " + future.get());
executor.shutdown(); // 스레드 풀 종료
}
}
주요 주의사항
- 경쟁 상태 (Race Condition): 동기화되지 않은 공유 자원 접근으로 인해 데이터 불일치가 발생할 수 있다.
- 데드락 (Deadlock): 두 개 이상의 스레드가 서로의 자원을 무한히 기다리는 상태를 주의해야 한다.
- 오버헤드: 너무 많은 스레드 생성은 문맥 교환(Context Switch) 비용을 증가시켜 오히려 성능을 저하시킬 수 있다.
P.S
멀티스레딩은 애플리케이션의 성능과 응답성을 높이는 핵심 기술이다.
synchronized와 같은 기초적인 도구부터 java.util.concurrent 패키지의 고수준 API까지 숙달하여 안전하고 효율적인 병렬 시스템을 설계해야 하는 것 같다.