[2026] Java 멀티스레드 | Thread, Runnable, Executor

[2026] Java 멀티스레드 | Thread, Runnable, Executor

이 글의 핵심

class MyThread extends Thread { private String name; public MyThread(String name) { this.name = name; } @Override…

들어가며

Thread·Executor·synchronized 등으로 여러 실행 흐름을 다룹니다. 공유 객체에 대한 접근 순서를 정하지 않으면 데이터가 깨질 수 있으므로, 락·원자 변수·동시성 유틸과 함께 읽는 것이 좋습니다. C++ std::thread·mutexOS 스레드 + 락이라는 큰 그림이 비슷해 비교하기 좋고, Go 고루틴처럼 스레드보다 가벼운 단위를 쓰는 언어와는 설계 선택이 다릅니다.

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

1. Thread 생성

방법 1: Thread 상속

다음은 java를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class MyThread extends Thread {
    private String name;
    
    public MyThread(String name) {
        this.name = name;
    }
    
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + ": " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Main {
    public static void main(String[] args) {
        MyThread t1 = new MyThread("Thread-1");
        MyThread t2 = new MyThread("Thread-2");
        
        t1.start();
        t2.start();
    }
}

방법 2: Runnable 구현 (권장)

다음은 java를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class MyRunnable implements Runnable {
    private String name;
    
    public MyRunnable(String name) {
        this.name = name;
    }
    
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + ": " + i);
        }
    }
}
// 사용
Thread t1 = new Thread(new MyRunnable("Thread-1"));
Thread t2 = new Thread(new MyRunnable("Thread-2"));
t1.start();
t2.start();
// 람다로 더 간결하게
Thread t3 = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        System.out.println("Thread-3: " + i);
    }
});
t3.start();

2. ExecutorService

스레드 풀

다음은 java를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import java.util.concurrent.*;
public class ExecutorExample {
    public static void main(String[] args) {
        // 고정 크기 스레드 풀
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("Task " + taskId + " by " + 
                    Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        
        executor.shutdown();
        
        try {
            executor.awaitTermination(1, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Future와 Callable

import java.util.concurrent.*;
public class FutureExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        
        // Callable: 반환값 있음
        Callable<Integer> task = () -> {
            Thread.sleep(1000);
            return 42;
        };
        
        Future<Integer> future = executor.submit(task);
        
        System.out.println("작업 진행 중...");
        
        // 결과 대기
        Integer result = future.get();  // 블로킹
        System.out.println("결과: " + result);
        
        executor.shutdown();
    }
}

3. 동기화 (Synchronization)

synchronized 메서드

다음은 java를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
        
        t1.start();
        t2.start();
        
        t1.join();
        t2.join();
        
        System.out.println("Count: " + counter.getCount());  // 2000
    }
}

synchronized 블록

다음은 java를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class BankAccount {
    private int balance = 0;
    private final Object lock = new Object();
    
    public void deposit(int amount) {
        synchronized (lock) {
            balance += amount;
        }
    }
    
    public void withdraw(int amount) {
        synchronized (lock) {
            if (balance >= amount) {
                balance -= amount;
            }
        }
    }
    
    public int getBalance() {
        synchronized (lock) {
            return balance;
        }
    }
}

4. 실전 예제

예제: 병렬 다운로더

다음은 java를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import java.util.concurrent.*;
import java.util.*;
public class ParallelDownloader {
    public static void main(String[] args) throws Exception {
        List<String> urls = Arrays.asList(
            "https://example.com/file1.txt",
            "https://example.com/file2.txt",
            "https://example.com/file3.txt"
        );
        
        ExecutorService executor = Executors.newFixedThreadPool(3);
        List<Future<String>> futures = new ArrayList<>();
        
        for (String url : urls) {
            Future<String> future = executor.submit(() -> {
                System.out.println("다운로드 시작: " + url);
                Thread.sleep(1000);  // 다운로드 시뮬레이션
                return "완료: " + url;
            });
            futures.add(future);
        }
        
        for (Future<String> future : futures) {
            System.out.println(future.get());
        }
        
        executor.shutdown();
    }
}

정리

핵심 요약

  1. Thread: 스레드 생성, start()로 실행
  2. Runnable: 작업 정의, 람다 사용 가능
  3. ExecutorService: 스레드 풀, 재사용
  4. Future/Callable: 반환값 있는 작업
  5. synchronized: 동기화, 데이터 무결성

다음 단계


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3