[260519]쓰레드(Thread)

이상민·2026년 5월 19일

Spring

목록 보기
11/59

목차

쓰레드(Thread)란

쓰레드란 프로그램(프로세스) 내에서 실제 작업을 수행하는 하나의 실행 단위이다.
쓰레드에는 싱글 쓰레드와 멀티 쓰레드가 있다.
이에 대한 비유로 프로세스를 식당으로 쓰레드를 직원으로 비유하자면,
싱글 쓰레드는 한명의 직원이 작업을 순차적으로 처리하는 실행 방식, 멀티 쓰레드는 여러 명의 직원이 여러 작업을 동시에 처리하는 실행 방식이라 이해하면 편하다.

싱글쓰레드

싱글쓰레드는 한 개의 쓰레드가 작업을 처리하는 것이다.
이는 직원이 한명이기 때문에 해야하는 작업이 여러개라면 순차적으로 처리해야한다.
main()메서드 또한 프로그램 시작과 동시에 생성되는 하나의 쓰레드라 볼 수 있다.

예시코드

public class Main {

    public static void main(String[] args) {
        System.out.println("main 쓰레드 시작");
        String threadName = Thread.currentThread().getName();
        
        //하나의 작업 단위: 숫자를 0 부터 9 까지 출력
        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("작업 끝");
    }
}
//실행결과
main 쓰레드 시작
현재 쓰레드: main - 0
현재 쓰레드: main - 1
현재 쓰레드: main - 2
.
.

.
현재 쓰레드: main - 9
작업 끝

이와 같이 쓰레드를 하나만 사용하는것을 싱글쓰레드라고 한다. 또한 sleep()을 사용하여 현재 실행중인 쓰레드를 일정 시간(ms)동안 정지시킬 수 있다.

요약

  • 싱글쓰레드는 하나의 직원
  • 해야 할 일이 많으면 순차적으로 해결

멀티쓰레드

멀티쓰레드는 여러개의 쓰레드가 동시에 작업을 처리하는 방식이다.
이를 활용하면 여러 작업을 동시에 처리할 수 있게된다.
Thread클래스를 상속받아 구현할 수 있으며, run()메서드를 오버라이드 하여 쓰레드가 수행할 작업을 정의할 수 있다.

예시코드

public class MyThread extends Thread {

    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + "쓰레드 시작");
        
        //하나의 작업 단위: 숫자를 0 부터 9 까지 출력
        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("작업 끝");
    }
}
public static void main(String[] args) {
	System.out.println("main 쓰레드 시작");
    
    MyThread thread0 = new MyThread();
    MyThread thread1 = new MyThread();
    
    System.out.println("thread0 시작");
    thread0.start();
    
    System.out.println("thread1 시작");
    thread1.start();

	System.out.println("main 쓰레드 종료");
}

이 예시 코드는 main쓰레드에서 thread0과 thread1을 실행시켰을때 어떤 결과가 나올지 예상하는 코드이다.

//실행결과
main 쓰레드 시작
thread0 시작
thread1 시작
main 쓰레드 종료
Thread-0 쓰레드 시작
Thread-1 쓰레드 시작
현재 쓰레드: Thread-0 - 0
현재 쓰레드: Thread-1 - 0
현재 쓰레드: Thread-0 - 1
현재 쓰레드: Thread-1 - 1
.
.
.
현재 쓰레드: Thread-0 - 9
현재 쓰레드: Thread-1 - 9
작업 끝

코드를 실행시키면 이러한 결과가 나온다.
실행 결과를 보면 Thread0과 Thread1의 출력이 번갈아 나타나는데, 이를 통해 여려 개의 쓰레드를 생성했을시 작업이 동시에 진행된다는걸 알 수 있다.

요약

  • 멀티쓰레드는 여려명의 직원
  • 여려명의 직원이 동시에 작업을 처리

join()에 대하여

join()은 특정 쓰레드가 종료될 때까지 현재 실행중인 쓰레드를 대기시키는 메서드이다.
위의 예시 코드에서는 main() 쓰레드가 종료되고 Thread-0과 Thread-1이 실행되었는데 join()메서드를 사용하면 Thread0,1의 작업이 끝날 때 까지 main() 쓰레드를 대기시킬 수 있다.
이를 통해, main()쓰레드가 언제 종료되는지를 계산하여 총 작업시간을 구할 수 있다.

예시 코드

public class Main {

    public static void main(String[] args) {
        System.out.println("main 쓰레드 시작");
        MyThread thread0 = new MyThread();
        MyThread thread1 = new MyThread();

        // 시작시간 기록
        long startTime = System.currentTimeMillis();

		//쓰레드 시작
        System.out.println("thread0 시작");
        thread0.start();

        System.out.println("thread1 시작");
        thread1.start();

        //main 쓰레드 대기
        try {
            thread0.join();
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long endTime = System.currentTimeMillis();
        long totalTime = endTime - startTime;
        System.out.println("총 작업 시간: " + totalTime + "ms");
        System.out.println("main 쓰레드 종료");
    }
}
//실행결과
main 쓰레드 시작
thread0 시작
thread1 시작
Thread-0 시작
Thread-1 시작
현재 쓰레드: Thread-0 - 0
현재 쓰레드: Thread-1 - 0
현재 쓰레드: Thread-0 - 1
현재 쓰레드: Thread-1 - 1
.
.
.
현재 쓰레드: Thread-0 - 9
현재 쓰레드: Thread-1 - 9
작업 끝
총 작업 시간: 5000ms
main 쓰레드 종료

이처럼 join()을 사용하면 main()쓰레드가 thread0,1의 작업이 끝날 떄 까지 대기 시킬 수 있고, 모든 쓰레드의 작업이 완료 됐을때의 시간을 통해 전체 작업 시간을 측정할 수 있다.

Runnabble 인터페이스 활용하기

Thread는 쓰레드를 제어하기 위해 존재하는 클래스이다.
이를 상속받아 위와같이 MyThread를 구현하면 실행 로직과 쓰레드 제어 로직 두 가지 역할이 하나의 클래스에 존재하게 된다.
이를 방지하고자, Runnable 인터페이스를 사용하면 실행 로직을 별도로 구현할 수 있게 된다.

예시 코드

public class MyNewClass {

    public void printMessage() {
        System.out.println("MyClass 기능 실행");
    }
}
public class MyRunnable extends MyNewClass implements Runnable { 
//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();
            }
        }
    }
}
public class Main {

    public static void main(String[] args) {

        MyRunnable task = new MyRunnable();

        //기존 클래스를 유지하면서 확장해서 활용
        task.printMessage(); 

        Thread thread0 = new Thread(task);
        Thread thread1 = new Thread(task);

        thread0.start();
        thread1.start();
    }
}

이와 같이 Runnable을 사용하면 Thread를 상속하지 않아도 되므로, Java의 단일 상속 제한을 피하면서 기존 클래스 기능을 유지한 채 쓰레드를 사용할 수 있다.

profile
백앤드 개발 브이로그

0개의 댓글