본문 바로가기
이직&취업/Java 기초 상식

Thread vs Runnable, 무엇을 언제 써야 할까?

by journeylabs 2025. 4. 26.
728x90
반응형

목차

     

    자바에서 멀티스레딩을 구현할 때 ThreadRunnable은 핵심적인 두 가지 방법입니다. 이 글에서는 Thread와 Runnable의 주요 개념, 특징, 차이점, 원리, 실무에서의 활용 사례, 장단점, 그리고 주의사항을 자세히 다뤄보겠습니다. 초보자부터 실무 개발자까지 쉽게 이해할 수 있도록 구성했으니, 자바 멀티스레딩의 모든 것을 마스터하고 싶다면 끝까지 읽어보세요!


    1. Thread와 Runnable이란? 주요 개념 및 용어

    Thread의 정의

    Thread는 자바에서 스레드를 직접 생성하고 실행하기 위한 클래스입니다. java.lang.Thread를 상속받아 스레드를 구현하거나, Thread 객체를 생성해 실행할 작업을 정의합니다.

     

    Runnable의 정의

    Runnable은 스레드가 실행할 작업을 정의하는 인터페이스입니다. 단일 추상 메서드 run()을 제공하며, Thread 클래스나 스레드 풀에 전달해 실행합니다.

     

    주요 용어

    • 스레드(Thread): 프로그램 내에서 독립적으로 실행되는 작업 단위.
    • 멀티스레딩(Multithreading): 여러 스레드를 동시에 실행해 병렬 처리.
    • run() 메서드: 스레드가 실행할 작업을 정의하는 메서드.
    • 스레드 풀(Thread Pool): 스레드를 재사용해 성능을 최적화하는 기법.

    Thread와 Runnable의 특징

    • Thread:
      • 자바에서 스레드 단위로 작업을 수행할 수 있도록 제공되는 클래스
      • java.lang.Thread 클래스를 상속.
      • run() 메서드를 오버라이드하여 작업 정의
      • 스레드 자체를 나타내며, 실행 제어 메서드(start(), join() 등) 제공.
      • 직접 인스턴스 생성 가능.
    • Runnable:
      • 자바의 작업 정의 인터페이스
      • java.lang.Runnable 인터페이스를 구현하여 run() 메서드에 작업 정의.
      • 스레드가 실행할 작업만 정의.
      • Thread 객체 또는 ExecutorService에 전달해 실행.

    Thread vs Runnable 차이점

    항목  Thread  Runnable
    정의 클래스, 스레드 자체 인터페이스, 작업 정의
    구현 방식 상속(extends Thread) 구현(implements Runnable)
    다중 상속 가능 여부 불가 (자바는 다중 클래스 상속 X) 가능 (다중 인터페이스 구현 가능)
    객체 공유 불가능 (각 스레드는 별도 Thread 인스턴스) 가능 (하나의 Runnable 인스턴스를 여러 스레드가 공유)
    유연성 단일 상속 제한 다중 구현 가능
    재사용성 낮음(스레드별 객체 생성) 높음(작업 분리 가능)
    사용 사례 간단한 스레드 작업 스레드 풀, 복잡한 작업

    2. Thread와 Runnable의 원리와 구조

    Thread의 원리

    Thread 클래스는 스레드의 생명주기와 실행을 관리합니다. start() 메서드를 호출하면 JVM이 새로운 스레드를 생성하고, run() 메서드를 실행합니다.

     

    Thread 구현 예시

    public class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Running in thread: " + Thread.currentThread().getName());
        }
    
        public static void main(String[] args) {
            MyThread thread = new MyThread();
            thread.start(); // 새로운 스레드 시작
        }
    }
    • 설명: MyThread는 Thread를 상속받아 run() 메서드를 오버라이드. start() 호출로 새로운 스레드가 생성되어 run() 실행.

    Runnable의 원리

    Runnable은 작업을 정의하는 인터페이스로, Thread 객체에 전달되거나 ExecutorService로 실행됩니다. run() 메서드만 구현하며, 스레드 실행은 외부에서 제어합니다.

     

    Runnable 구현 예시

    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Running in thread: " + Thread.currentThread().getName());
        }
    
        public static void main(String[] args) {
            Runnable runnable = new MyRunnable();
            Thread thread = new Thread(runnable);
            thread.start(); // Runnable을 실행하는 스레드 시작
        }
    }
    
    • 설명: MyRunnable은 Runnable을 구현해 run() 정의. Thread 객체에 전달해 실행. Runnable은 작업만 정의하므로 재사용 가능.

    구조 비교

    • Thread: 스레드 객체와 작업이 결합. run()을 직접 오버라이드.
    • Runnable: 작업과 스레드가 분리. run()을 구현하고, Thread 또는 스레드 풀에 전달.

    Thread와 Runnable 조합

    Runnable runnable = () -> System.out.println("Lambda Runnable");
    new Thread(runnable).start();
    • 설명: 람다식을 사용해 Runnable을 간결히 정의. Thread에 전달해 실행.

    3. 실무에서 Thread와 Runnable 활용 사례

    Thread와 Runnable은 Spring Framework와 같은 환경에서 비동기 처리, 병렬 작업, 스케줄링 등에 사용됩니다.

    3.1. Spring Boot에서 Runnable 사용

    Spring Boot는 @Async와 ExecutorService를 통해 Runnable을 활용한 비동기 처리를 지원합니다.

     

    예시: 비동기 작업 처리

    @Service
    public class AsyncService {
        @Async
        public CompletableFuture<String> processTask(String task) {
            Runnable runnable = () -> {
                System.out.println("Processing task: " + task + " in thread: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // 작업 시뮬레이션
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            };
            new Thread(runnable).start();
            return CompletableFuture.completedFuture("Task " + task + " completed");
        }
    }
    
    @Configuration
    @EnableAsync
    public class AsyncConfig {
        @Bean
        public Executor taskExecutor() {
            ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, 4, 10, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100));
            return executor;
        }
    }
    
    • 설명: @Async로 비동기 메서드 정의. Runnable을 사용해 작업을 별도 스레드에서 실행. ThreadPoolExecutor로 스레드 풀 관리.

    3.2. Spring Scheduler와 Runnable

    Spring의 @Scheduled는 Runnable을 활용해 주기적인 작업을 수행합니다.

    @Component
    public class ScheduledTask {
        @Scheduled(fixedRate = 5000)
        public void runTask() {
            Runnable task = () -> {
                System.out.println("Scheduled task running in thread: " + Thread.currentThread().getName());
            };
            new Thread(task).start();
        }
    }
    • 설명: @Scheduled로 5초마다 실행되는 작업. Runnable로 작업을 정의해 별도 스레드에서 실행.

    3.3. 스레드 풀과 Runnable

    실무에서는 ExecutorService에 Runnable을 전달해 스레드 풀을 활용합니다.

    @Service
    public class TaskService {
        private final ExecutorService executor = Executors.newFixedThreadPool(4);
    
        public void executeTasks(List<String> tasks) {
            for (String task : tasks) {
                Runnable runnable = () -> {
                    System.out.println("Processing " + task + " in thread: " + Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                };
                executor.submit(runnable);
            }
        }
    
        @PreDestroy
        public void shutdown() {
            executor.shutdown();
        }
    }
    • 설명: ExecutorService에 Runnable을 제출해 병렬 작업 처리. 스레드 풀로 자원 효율성 향상.

    4. Thread와 Runnable의 장점과 단점

    Thread의 장단점

    • 장점:
      • 간단한 구현: 간단한 스레드 작업에 적합.
      • 직접 제어: start(), join() 등 스레드 제어 메서드 제공.
      • 직관적: 스레드와 작업이 하나로 통합.
    • 단점:
      • 단일 상속: 자바는 단일 상속만 지원하므로 유연성 제한.
      • 재사용성 낮음: 작업과 스레드가 결합되어 재사용 어려움.
      • 자원 낭비: 스레드 객체를 반복 생성하면 메모리 소모.

    Runnable의 장단점

    • 장점:
      • 유연성: 인터페이스 구현으로 다중 상속 가능.
      • 재사용성: 작업과 스레드 분리, 스레드 풀과 조합 가능.
      • 확장성: ExecutorService 등 현대적인 스레드 관리 기법과 호환.
    • 단점:
      • 추가 코드: Thread 객체 또는 스레드 풀에 전달해야 함.
      • 복잡성: 스레드 풀 설정이나 관리에서 추가 학습 필요.

    주의사항

    • Thread 상속 지양: 단일 상속 제한과 재사용성 문제로 Runnable 선호.
    • 스레드 풀 사용: 반복적인 스레드 생성 대신 ExecutorService 활용.
    • 스레드 안전성: 공유 자원 접근 시 동기화(synchronized, Lock) 필요.
    • 예외 처리: Runnable의 run()은 체크 예외를 던질 수 없으므로 try-catch로 처리.
    • 자원 정리: 스레드 풀 종료(shutdown())와 인터럽트 처리 철저히.

    5. 결론

    언제 Thread, 언제 Runnable?

    상황 추천 방식
    매우 단순한 테스트 코드 Thread
    복잡한 구조, 클래스 다중 상속 필요 Runnable
    실무 비동기 작업, 공유 자원 사용 Runnable 권장

    실무에서는 대부분 Runnable 또는 Callable + ExecutorService 기반 구조를 사용합니다.

     

    ThreadRunnable은 자바 멀티스레딩의 핵심 구성 요소로, 각각의 특징과 사용 사례가 다릅니다. Thread는 간단한 작업에 적합하지만, 유연성과 재사용성 면에서 Runnable이 더 적합합니다. Spring Framework에서는 Runnable을 ExecutorService, @Async, @Scheduled와 조합해 비동기 처리와 스케줄링에 활용됩니다. 스레드 풀과 동기화 기법을 적절히 사용하면 성능과 안정성을 모두 확보할 수 있습니다.

    이 글을 통해 ThreadRunnable차이부터 실무 활용까지 완벽히 이해하셨길 바랍니다. 멀티스레딩을 활용해 더 효율적이고 강력한 애플리케이션을 구축해 보세요!

    728x90
    반응형