CS

[CS] 스레드(Thread), 멀티 스레드(Multi-Thread)

kimyongjun0129 2025. 4. 18. 10:01

 

 


 

 

스레드(Thread)

프로그램 내에서 독립적으로 실행되는 하나의 작업 단위이다. (요리사)

 

 

싱글 스레드(Single Thread)

하나의 프로그램 내에서 하나의 스레드를  통해 하나의 작업을 처리한다. (요리사 한 명)

  • 요리사가 한 명이기 때문에 여러 개의 주문이 들어온다면, 순차적으로 처리해야 한다. 

 

멀티 스레드(Multi-Thread)

하나의 프로그램 내에서 여러 개의 스레드를 만들어 병렬 또는 동시성 있게 수행하는 기능이다. 즉, 프로세스 내에서 여러 개의 흐름이 동시에 동작하는 것이다. (요리사 여러 명)

  • 요리사가 여러 명이기 때문에 여러 개의 주문이 들어온다면, 동시에 주문을 처리할 수 있다.
  • 자바는 기본적으로 main 스레드 하나로 동작한다.

 


 

 

멀티 스레드를 사용하는 이유

  • 무거운 작업 진행 시, 병렬로 처리하기 위해서이다.
  • CPU 자원을 효율적으로 사용 가능 
    • 멀티 코어 CPU의 경우 각각의 스레드를 여러 코어에 분산시켜 병렬 처리한다.
    • 프로그램은 I/O 작업(예: 파일 읽기, 네트워크 요청) 중에 대기해야 하는데, 단일 스레드는 대기 시간동안 멈춰있지만, 멀티 스레드는 대기 시간 동안 다른 스레드를 실행 가능하다.
    • 큰 작업을 여러 개의 작은 작업으로 나눠서 각 스레드에 분산하면 전체 처리 속도가 증가한다.
  • 여러 작업을 동시에 처리하는 것처럼 보여주기 (예: 다운로드 + 영상 재생)

 

❗쉽게 말해서,

  • 단일 스레드한 명의 요리사가 하나씩 요리를 만든다.
  • 멀티 스레드여러 명의 요리사가 동시에 다른 요리를 만든다.
  • 부엌(CPU)을 놀리지 않고 계속 돌리는 것이다.

 


 

스레드 주요 개념

개념 설명
start() 메서드 스레드를 새로 시작한다. (⚠️ run()을 직접 호출하면 멀티스레드 아님)
run() 메서드 스레드가 실행할 작업 내용
sleep(ms) 메서드 일정 시간 동안 스레드를 일시 정지
join() 메서드 다른 스레드가 끝날 때까지 기다림
synchronized 키워드 여러 스레드가 공유 자원에 동시에 접근하지 않도록 동기화 처리

 

 


 

싱글 스레드 사용 예제

더보기
public class Main {

    public static void main(String[] args) {
        System.out.println("::: main 쓰레드 시작 :::");
        String threadName = Thread.currentThread().getName();   // 현재 스레드 이름 가져오기
        
        // ✅ 하나의 작업 단위: 숫자를 0 부터 4 까지 출력
        for (int i = 0; i < 10; i++) {
            System.out.println("현재 쓰레드: " + threadName + " - " + i);
            try {
                Thread.sleep(500); // 0.5 초 대기
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // ✅ 추가작업
        for (int i = 0; i < 10; i++) {
            System.out.println("현재 쓰레드: " + threadName + " - " + i);
            try {
                Thread.sleep(500); // 0.5 초 대기
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("::: 작업 끝 :::");
    }
}

싱글 스레드이므로 하나의 작업이 끝나야 다음 작업을 진행할 수 있다.

 

콘솔값

 

 


 

 

멀티 스레드 사용 예제

더보기

MyThread Class

// ✅ Thread 클래스 상속으로 쓰레드 구현
public class MyThread extends Thread {

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println("::: " + threadName + "쓰레드 시작 :::");
        for (int i = 0; i < 5; i++) {
            System.out.println("현재 쓰레드: " + threadName + " - " + i);
            try {
                Thread.sleep(500); // 딜레이 0.5 초
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("::: " + threadName + "쓰레드 종료 :::");
    }
}

 

Main

public class Main {

    public static void main(String[] args) {
        System.out.println("::: main 쓰레드 시작");

        MyThread thread0 = new MyThread();
        MyThread thread1 = new MyThread();

        // 1. thread0 실행
        System.out.println("::: main 이 thread0 을 실행");
        thread0.start();

        // 2. thread1 실행
        System.out.println("::: main 이 thread1 을 실행");
        thread1.start();

        System.out.println("::: main 쓰레드 종료");
    }
}
  • `Thread Class`를 상속받아 `나만의 Thread Class`를 만들 수 있다.
    • 하지만 `MyThread`는 다른 Class를 더이상 상속받지 못하게 된다. (다이아 몬드 상속)
  • 멀티 스레드이므로 작업을 병렬로 처리할 수 있다.

 

콘솔값

 


 

 

Runnable 인터페이스 활용(권장)

자바에서는 `Runnable 인터페이스`를 활용해 스레드를 구현하는 것을 권장한다.

 

1.유지 보수성과 재사용성 향상

  • Thread 클래스스레드를 제어하기 위해 존재하는 클래스이다.
  • Thread 클래스를 상속받아 MyThread를 구현하면 실행 로직과 스레드 제어 로직이 결합되어 한 가지 클래스에서 두 가지 역할을 담당하게 된다.
    • 스레드 제어 로직 : `start()`,` join()`, `isAlive()` 등
    • 실행 로직 : 숫자 0 ~ 9 출력
    • 하나의 클래스는 하나의 책임만 가지는 것이 유지보수에 좋다.
  • Runnable을 활용하면 실행 로직을 별도의 구현체로 분리할 수 있다.
    • Thread는 쓰레드를 제어하는 역할
    • Runnable 구현체는 실행 로직을 관리
더보기

MyRunnable Class

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        for (int i = 0; i < 10; i++) {
            System.out.println("현재 쓰레드: " + threadName + " - " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

Main

public class Main {

    public static void main(String[] args) {

        MyRunnable task = new MyRunnable(); // ✅ 하나의 작업 객체 선언

        // ✅ 하나의 작업을 여러 쓰레드에서 공유
        Thread thread0 = new Thread(task); // 작업 객체 공유
        Thread thread1 = new Thread(task); // 작업 객체 공유

        // 실행
        thread0.start();
        thread1.start();
    }
}

 

위 Main에서 thread0의 객체를 생성할 때,  MyRunnable 타입의 task 변수를 thread0의 생성자 파라미터로 넘겨주었다.

 

Thread 생성자

  • Thread Class의 생성자를 통해, Runnable 타입의 객체를 넘겨받는다. Runnable은 MyRunnable의 부모이기 때문에, `MyRunnable`을 매개 변수로 받아 올 수 있다.
  • `new Thread(task)`를 통해 Runnable 객체를 스레드에 넘겨줌으로써, Thread 객체가 실행될 때 `task.run()`이 호출되도록 설정하는 과정중 하나이다.

 

Runnable 인터페이스

MyRunnable Class는 함수형 인터페이스인 Runnable의 run 메서드를 구현한다.

 

 

 

 

 

2. 기존 클래스 유지, 확장 가능

  • Thread를 상속해서 MyThread를 구현하면, 다른 클래스를 상속받지 못한다.
    • 자바는 다이아 몬드 상속으로 인해, 다중 상속을 지원하지 않는다.
    • Thread를 상속하면 다른 클래스를 상속할 수 없어서 확장성이 떨어진다.
  • Runnable은 인터페이스이므로 기존 클래스의 기능을 유지하면서 상속을 통해 확장 가능