스레드(Thread)
Java에서 멀티 스레드를 실행하는 방법에는 두 가지가 있다.
1️⃣ 기본적인 쓰레드 생성
1.Thread 클래스를 상속하는 방식
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 실행 중");
}
}
public class ThreadExample {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start(); // 새로운 쓰레드 실행
}
}
장점: 코드가 직관적
단점: Thread를 상속하면 다른 클래스를 상속할 수 없음
2. Runnable 인터페이스를 구현하는 방식 (권장)
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 실행 중");
}
}
public class RunnableExample {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
장점: 다른 클래스를 상속할 수 있음
가장 많이 사용하는 방식!
3. Runnable을 람다식으로 표현 (가장 간결한 방식)
public class LambdaThreadExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName() + " 실행 중"));
thread.start();
}
}
2️⃣ 쓰레드 동기화 (Race Condition 해결)
여러 스레드가 공유 자원(Shared Resource)에 동시에 접근하면 Race Condition 발생 가능.
- 이를 방지하기 위해서 synchronized 또는 ReentrantLock을 사용
1. synchronized 키워드 사용
class Counter {
private int count = 0;
public synchronized void increment() { // 동기화 적용
count++;
}
public int getCount() {
return count;
}
}
한 번에 하나의 스레드만 접근 가능
간단한 동기화 처리에 적합
2. ReentrantLock 사용 (더 정교한 동기화)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class LockCounter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
더 세밀한 제어 가능 (락 획득/해제 제어 가능)
여러 개의 락을 관리해야 할 때 유용함
3️⃣ volatile - 캐시 일관성 문제 해결
멀티 쓰레드 환경에서는 변수 값이 CPU 캐시에 저장될 수 있어 다른 스레드가 최신 값을 읽지 못하는 문제 발생 가능.
- volatile 키워드를 사용하면 항상 메모리에서 최신 값을 읽음.
class VolatileExample {
private volatile boolean running = true; // volatile 사용
public void stop() {
running = false;
}
public void runTask() {
while (running) { }
System.out.println("스레드 종료");
}
}
단순한 플래그 변수(boolean running)에는 volatile을 사용하면 효과적
여러 연산이 필요한 경우 synchronized 사용 필요
4️⃣ Executor 프레임워크 (쓰레드 풀)
쓰레드를 직접 생성하면 비효율적이므로, Executor를 사용하여 쓰레드 관리를 자동화할 수 있음.
1. FixedThreadPool (고정된 쓰레드 개수)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3); // 쓰레드 3개만 유지
for (int i = 1; i <= 10; i++) {
final int taskId = i;
executor.submit(() -> System.out.println("작업 " + taskId + " 실행"));
}
executor.shutdown();
}
}
쓰레드 개수를 제한하여 과부하 방지
CPU 바운드 작업에 적합
5️⃣ CompletableFuture (비동기 프로그래밍)
➡ Future의 단점을 개선한 비동기 프로그래밍 도구.
➡ 콜백 기반으로 작업을 체이닝할 수 있음.
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExample {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(result -> result + " World") // 결과 변환
.thenAccept(System.out::println); // 최종 결과 출력
}
}
비동기 작업을 순차적으로 실행 (thenApply(), thenAccept())
여러 개의 비동기 작업을 조합할 수 있음 (thenCompose(), thenCombine())
6️⃣ VirtualThread (가상 쓰레드)
➡ Java 21에서 정식 추가된 경량 쓰레드
➡기존 쓰레드보다 훨씬 가볍고 대량의 동시성을 처리 가능
| 플랫폼 쓰레드 (Platform Thread) | 가상 쓰레드 (Virtual Thread) | |
| 쓰레드 개수 | 운영체제의 리소스에 따라 제한적 | 수백만 개까지 생성 가능 |
| 컨텍스트 스위칭 비용 | 높음 (커널 모드 전환 필요) | 낮음 (유저 모드에서 스위칭) |
| 블로킹 작업 | 비효율적 (쓰레드가 대기 상태) | 효율적 (Carrier Thread가 다른 작업 수행 가능) |
| 적용 사례 | CPU 바운드 작업 | I/O 바운드 작업 (예: 웹 서버, DB 연결) |
기존의 플랫폼 쓰레드는 OS 쓰레드 하나당 Java 쓰레드 하나가 매핑되는 반면, 가상 쓰레드는 OS 쓰레드
여러 개를 공유하며 실행됨.
1. 가상 쓰레드 실행
public class VirtualThreadExample {
public static void main(String[] args) {
Thread.ofVirtual().start(() -> System.out.println("가상 쓰레드 실행!"));
}
}
기존 Thread API와 동일한 방식으로 사용 가능
2. 가상 쓰레드 + Executor
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class VirtualThreadExecutorExample {
public static void main(String[] args) {
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 1000; i++) {
executor.submit(() -> System.out.println("작업 실행: " + Thread.currentThread()));
}
}
}
}
수백만 개의 가상 쓰레드 실행 가능
| 개념 | 주요 기능 |
| 기본 쓰레드 | Thread, Runnable |
| 동기화 | synchronized, ReentrantLock |
| 메모리 일관성 | volatile |
| 쓰레드 풀 | FixedTreadPool, CachedThreadPool |
| 비동기 처리 | CompletableFuture |
| 가상 쓰레드 | Thread.ofVirtual(), Executors.newVirtualThreadPerTaskExecutor() |
'TIL' 카테고리의 다른 글
| [250313 TIL] 키오스크 기능 추가 및 개선 (2) | 2025.03.13 |
|---|---|
| [250312 TIL] JAVA 키오스크 프로젝트 개선 (2) | 2025.03.12 |
| [250310 TIL] 스트림으로 리스트 중복 개수 세기 (3) | 2025.03.10 |
| [2050307 TIL] 키오스크 과제 (3) | 2025.03.07 |
| [250305 TIL] 파일 이름 바꾸기 리팩토링 (2) | 2025.03.06 |