스레드(Thread)는 하나의 프로세스 내에서 독립적으로 실행되는 실행 흐름 단위를 말합니다. 일반적으로 하나의 프로그램(프로세스)은 하나 이상의 스레드를 가질 수 있으며, 이를 통해 동시에 여러 작업을 처리하는 멀티스레딩(Multithreading)이 가능합니다. 예를 들어 음악을 재생하면서 동시에 파일을 다운로드하거나 사용자 입력을 처리하는 등의 작업을 병렬로 수행할 수 있습니다. 자바에서는 Thread 클래스를 상속하거나 Runnable 인터페이스를 구현하여 스레드를 정의하고 실행할 수 있으며, 효율적인 CPU 자원 활용과 반응성 향상에 유리하지만, 스레드 간 자원 공유로 인해 동기화(synchronization)와 같은 주의가 필요합니다.
class MyThread extends Thread {
@Override
public void run() {
System.out.println("스레드 실행 중! - " + Thread.currentThread().getName());
}
}
public class ThreadExample1 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // run()이 아닌 start()를 호출해야 새 스레드가 생성됨
}
}
class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable 스레드 실행 중! - " + Thread.currentThread().getName());
}
}
public class ThreadExample2 {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start();
}
}
public class AnonymousThreadExample1 {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
System.out.println("익명 Thread 클래스 실행!");
}
};
t.start();
}
}
public class AnonymousThreadExample2 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("익명 Runnable 객체 실행!");
}
});
t.start();
}
}
public class LambdaThreadExample {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("람다식으로 만든 스레드 실행!");
});
t.start();
}
}
class PrintTask implements Runnable {
private String message;
public PrintTask(String message) {
this.message = message;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(message + " - " + i);
}
}
}
public class MultiThreadTest {
public static void main(String[] args) {
Thread t1 = new Thread(new PrintTask("🍎 김사과"));
Thread t2 = new Thread(new PrintTask("🍌 반하나"));
t1.start();
t2.start();
}
}
여러 스레드가 공유 자원을 동시에 수정하면 충돌이 발생할 수 있습니다. synchronized 키워드는 한 번에 한 스레드만 메서드에 접근하도록 합니다.
class Counter {
private int count = 0;
public void increment() {
count++; // 동시에 접근 시 문제 발생 가능
}
public int getCount() {
return count;
}
}
public synchronized void methodName() {
// 이 메서드는 한 번에 하나의 스레드만 실행 가능
}
public void methodName() {
synchronized (this) {
// this: 현재 인스턴스를 lock
}
}
class SafeCounter {
private int count = 0;
public synchronized void increment() {
count++; // 메서드에 synchronized → 한 번에 하나의 스레드만 실행
}
public int getCount() {
return count;
}
}
public class SyncExample {
public static void main(String[] args) throws InterruptedException {
SafeCounter counter = new SafeCounter();
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
counter.increment(); // 안전하게 동작
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("최종 카운트: " + counter.getCount()); // 항상 20000
}
}
Thread.join() 메서드는 다른 스레드가 종료될 때까지 현재 스레드(보통 main 스레드)가 기다리게 만드는 메서드입니다. 즉, t1.join();은 현재 실행 중인 스레드(main 등)가 t1이 끝날 때까지 멈춰서 기다리도록 강제합니다.
스레드 풀(Thread Pool)은 미리 생성된 스레드들을 풀(pool)에 담아두고, 작업이 생기면 재사용하는 방식입니다. 매번 새 스레드를 생성하는 것이 아니라 재사용하여 성능을 향상시키고 자원 낭비를 줄입니다.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3); // 최대 3개 스레드 사용
for (int i = 1; i <= 5; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("작업 " + taskId + "을 실행 중 (스레드: " + Thread.currentThread().getName() + ")");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
System.out.println("작업 " + taskId + " 완료");
});
}
executor.shutdown(); // 작업 제출은 종료 (스레드가 자동 종료되도록)
}
}
import java.util.concurrent.*;
public class CallableFutureExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Integer> task = () -> {
System.out.println("복잡한 계산 중...");
Thread.sleep(2000); // 2초 지연
return 42; // 결과값 반환
};
Future<Integer> future = executor.submit(task);
System.out.println("메인 스레드는 다른 작업 중...");
Integer result = future.get(); // 결과가 준비될 때까지 대기
System.out.println("계산 결과: " + result);
executor.shutdown();
}
}
여러 Callable<T> 작업을 한꺼번에 제출하고, 모든 작업이 완료될 때까지 대기한 뒤 각 작업의 결과를 List<Future<T>>로 반환합니다.
import java.util.*;
import java.util.concurrent.*;
public class InvokeAllExample {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(3);
// 3개의 Callable 작업 생성
List<Callable<String>> tasks = List.of(
() -> { Thread.sleep(500); return "🍎 사과"; },
() -> { Thread.sleep(300); return "🍌 바나나"; },
() -> { Thread.sleep(700); return "🍇 포도"; }
);
// 모든 작업이 끝날 때까지 대기
List<Future<String>> futures = executor.invokeAll(tasks);
// 결과 출력
for (Future<String> f : futures) {
try {
System.out.println("결과: " + f.get());
} catch (ExecutionException e) {
System.out.println("작업 중 예외 발생: " + e.getCause());
}
}
executor.shutdown();
}
}
여러 Callable<T> 작업을 제출하고, 가장 먼저 완료된(성공적으로 반환된) 하나의 결과만 리턴합니다. 나머지 작업은 취소됩니다.
import java.util.*;
import java.util.concurrent.*;
public class InvokeAnyExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
List<Callable<String>> tasks = List.of(
() -> { Thread.sleep(500); return "🍎 사과"; },
() -> { Thread.sleep(300); return "🍌 바나나"; },
() -> { Thread.sleep(700); return "🍇 포도"; }
);
try {
// 가장 먼저 끝난 작업의 결과만 리턴
String result = executor.invokeAny(tasks);
System.out.println("가장 빠른 결과: " + result);
} catch (InterruptedException | ExecutionException e) {
System.out.println("예외 발생: " + e.getMessage());
} finally {
executor.shutdown();
}
}
}
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
class Order {
private final int orderId;
public Order(int orderId) {
this.orderId = orderId;
}
public int getOrderId() {
return orderId;
}
}
class OrderProcessor implements Callable<String> {
private final Order order;
private final String workerName;
private static final Random random = new Random();
// 점원별 처리 속도 범위 (단위: 밀리초)
private static final Map<String, int[]> workerSpeedMap = Map.of(
"김사과", new int[]{1000, 2000}, // 빠름
"반하나", new int[]{2000, 3000}, // 보통
"오렌지", new int[]{3000, 4000} // 느림
);
private static final AtomicInteger totalProcessed = new AtomicInteger(0);
private static final Map<String, Integer> workerStats = new ConcurrentHashMap<>();
public OrderProcessor(Order order, String workerName) {
this.order = order;
this.workerName = workerName;
}
@Override
public String call() throws Exception {
int[] speedRange = workerSpeedMap.get(workerName);
int prepTime = random.nextInt(speedRange[1] - speedRange[0] + 1) + speedRange[0];
Thread.sleep(prepTime);
totalProcessed.incrementAndGet();
workerStats.merge(workerName, 1, Integer::sum);
return workerName + " - 주문 " + order.getOrderId() + "번 완료 (소요시간: " + prepTime + "ms)";
}
public static int getTotalProcessed() {
return totalProcessed.get();
}
public static void printStats() {
System.out.println("\n🧾 점원별 처리 주문 수:");
workerStats.forEach((name, count) -> System.out.println("👨🍳 " + name + ": " + count + "건"));
}
}
public class BurgerOrderSimulator {
public static void main(String[] args) throws InterruptedException, ExecutionException {
int totalOrders = 10;
List<String> workers = List.of("김사과", "반하나", "오렌지");
ExecutorService executor = Executors.newFixedThreadPool(workers.size());
List<Future<String>> futures = new ArrayList<>();
for (int i = 1; i <= totalOrders; i++) {
String worker = workers.get(i % workers.size());
Order order = new Order(i);
futures.add(executor.submit(new OrderProcessor(order, worker)));
}
for (Future<String> future : futures) {
System.out.println(future.get());
}
executor.shutdown();
OrderProcessor.printStats();
System.out.println("총 주문 처리 수: " + OrderProcessor.getTotalProcessed() + "건 🍔");
}
}