TIL

[250311 TIL] Thread 좀 더 공부해보기

도원좀비 2025. 3. 11. 20:09

스레드(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()