JAVA

[JAVA] 스레드(Thread) 정리

tudamoa 2025. 3. 1. 20:13

먼저 스레드(Thread)에 대해 알기 전 프로세스(Process)에 대해 알아둘 필요가 있다.

 

1. 프로세스(Process) 란?

컴퓨터의 보조 기억 장치(HDD, SSD)에는 다양한 프로그램이 저장되어 있다. 우리가 이 프로그램을 실행하면, 운영체제(OS)가 이를 메모리(RAM)에 로드하고 실행을 관리한다. 이때, 메모리에 적재되어 실행 중인 프로그램을 프로세스(Process)라고 한다.

 

자바 애플리케이션이 실행되면 JVM(Java Virtual Machine)이 하나의 프로세스를 생성하여 프로그램을 실행한다. 하지만 프로세스가 단 하나의 작업만 수행한다면 애플리케이션의 효율성이 떨어질 것이다.

 

예를 들어, 웹 서버처럼 여러 요청을 동시에 처리해야 하는 프로그램에서는 하나의 프로세스가 여러 작업을 동시에 수행할 필요가 있다. 이를 위해 자바는 멀티스레드(Multi-Thread) 기능을 제공하여, 하나의 프로세스 내에서 여러 스레드가 동시에 실행될 수 있도록 한다.

 

즉, 프로세스는 하나 이상의 스레드를 포함하는 구조(프로세스 ⊃ 스레드)를 갖는다.

 

2. 스레드(Thread) 란?

스레드는 프로세스 내부에서 독립적으로 실행되는 최소 작업 단위이다.

각 프로세스는 자원(메모리 공간, 파일, 네트워크)이 독립적으로 할당되어 있다.

하지만 스레드는 같은 프로세스 내에서 실행되며, 해당 프로세스의 자원을 공유하며 각자의 작업을 수행한다.

 

2-2. 멀티 스레드(Multi-Thread)

하나의 프로세스에서 여러 개의 스레드가 동시에 작업을 수행하는 것으로, 시스템의 성능을 향상시킬 수 있다.

하지만 여러 개의 스레드가 같은 자원을 동시에 공유하기 때문에 충돌과 동기화 문제를 발생시킬 수도 있다.

 

반대로 단일 스레드는(Single-Thread) 한 프로세스에서 한 스레드만 순차적으로 실행하는 것이다.

(효율성은 좋지 않지만, 프로그래밍이 쉽고 안정성이 높다는 장점이 있다)

 

3. 스레드(Thread) 구조

스레드 상태

상태 설명
NEW 스레드가 실행 준비가 된 상태
RUNNABLE 스레드가 실행이 가능한 상태
RUNNING 실행 중인 스레드
BLOCKED 스레드가 차단되어 있는 상태
WAITING 스레드가 대기 중인 상태
TIMED_WAITING 스레드가 일정 시간 동안 대기하는 상태
TERMINATED 스레드가 종료된 상태

 

스레드 메서드

반환 타입 메서드 설명
void start() 스레드를 시작하고 run() 메서드 호출
void run() 스레드의 작업을 정의
void sleep(long ms) 인자로 받은 ms만큼 일시 중지
void suspend() 스레드 일시정지 (resume()을 호출하지 않으면 데드락 발생) | 비추
void resume() 스레드 다시시작
void wait()  스레드 일시정지 (synchronized 블록 안에서 호출 가능) | 추천
void notify() / notifyAll() 대기 상태 스레드를 준비 상태로 변환
void join() 다른 스레드가 끝날 때 까지 대기
void yield()  스레드를 준비 상태로 변환, 다른 스레드에게 실행 양보  
void interrupt() 스레드 중지
boolean  isAlive()  스레드가 실행 중이면 True, 아니면 False
void setName() 스레드 이름 지정
String getName() 스레드 이름 반환
void setPriority() 스레드 우선순위 지정
int getPriority() 스레드 우선순위 반환
Thread currentThread() 현재 실행 중인 스레드 반환
Thread.State getState() 스레드 상태 반환

 

4. 스레드(Thread) 실행

스레드를 실행하는 방법에는 Thread 클래스를 상속받아 실행하는 방법과 Runnable 인터페이스를 상속받는 법이 있다.

하지만 자바의 특성상 다중 상속이 되지 않기 때문에 Thread 클래스를 직접적으로 상속받아 제약을 만들기보단

객체 지향적 특성을 더 가져가기 위해  Runnable 인터페이스를 상속받는 방법을 더 추천한다.

 

1. 기본적인 스레드 실행 ( start(), run() )

class MyRunnable implements Runnable {
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + " 실행 중: " + i);
            try {
                Thread.sleep(1000); // 1초 동안 대기
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " interrupted!");
            }
        }
        System.out.println(Thread.currentThread().getName() + " 종료");
    }
}

public class Main {
    public static void main(String[] args) {
        Runnable task = new MyRunnable();

        Thread t1 = new Thread(task, "스레드1");
        Thread t2 = new Thread(task, "스레드2");

        t1.start(); // 새로운 스레드 실행 (run() 내부적으로 호출 | 순서없이 실행)
        t2.start();

//        t1.run(); // 직접적으로 main 메서드 실행 (순서대로 실행된다)
//        t2.run();
    }
}

 

결과를 보면 계속 달라지는 것을 확인할 수 있다.

Main 메서드에서 start() 메서드를 통해 호출을 한다면 새로 생성한 스레드들로 멀티 스레드가 되지만,

직접적으로 run() 메서드를 호출하면 main 스레드로 순서대로 실행이 된다.

 

2. 스레드 일시 중지 ( sleep() )

class SleepRunnable implements Runnable {
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println(Thread.currentThread().getName() + " "+ i);
            try {
                Thread.sleep(1000); // 1초 대기
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getState());
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Runnable task = new SleepRunnable(); // Runnable 객체 생성
        Thread t1 = new Thread(task, "Sleep"); // Runnable을 Thread에 전달
        t1.start(); // 1초마다 출력됨
    }
}

 

3. 다른 스레드가 끝날 때까지 대기 ( join() )

class JoinRunnable implements Runnable {
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println(Thread.currentThread().getName() + " 실행 중: " + i);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Runnable task = new JoinRunnable();  // Runnable 객체 생성
        Thread t1 = new Thread(task, "JoinThread");  // Runnable을 Thread에 전달
        t1.start();

        try {
            t1.join();  // t1이 종료될 때까지 메인 스레드가 기다림
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("t1 종료 후 메인 스레드가 실행됨.");
    }
}

 

4. 스레드 상태 확인 ( isAlive(), getState() )

class StateRunnable implements Runnable {
    public void run() {
        try {
            Thread.sleep(1000); // 1초 동안 대기
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Runnable task = new StateRunnable();  // Runnable 객체 생성
        Thread t1 = new Thread(task, "StateThread");  // Runnable을 Thread에 전달

        System.out.println("초기 상태: " + t1.getState()); // start() 호출 전이므로 NEW 상태

        t1.start();
        System.out.println("실행 중 True/False: " + t1.isAlive()); // 실행 중이므로 True

        try {
            t1.join(); // t1이 종료될 때까지 대기
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("종료 후 상태: " + t1.getState());
        // join() 메서드로 확실히 종료된 지점이므로 TERMINATED 상태
    }
}

 

5. 스레드 일시 정지 / 재시작 ( wait() / notify() )

class Example {
    synchronized void waitMethod() {
        try {
            System.out.println(Thread.currentThread().getName() + " 대기");
            wait(); // 현재 스레드 일시 정지 (WAITING 상태)
            System.out.println(Thread.currentThread().getName() + " 재실행");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized void notifyMethod() {
        System.out.println("notify 호출: " + Thread.currentThread().getName());
        notify(); // 대기 중인 스레드 깨움
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Example obj = new Example();

        Thread t1 = new Thread(obj::waitMethod, "스레드1");
        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (obj) { // notify() 후 즉시 락을 해제하도록 synchronized 사용
                System.out.println("notify 호출: " + Thread.currentThread().getName());
                obj.notify();
            }

        }, "스레드2");

        System.out.println("t1 초기 상태: " + t1.getState()); // NEW
        t1.start();
        System.out.println("t1 실행 후 상태: " + t1.getState()); // RUNNABLE

        t2.start();
        t2.join(); // t2가 끝날 때까지 기다려서 BLOCKED 상태 방지
        System.out.println("t1 상태 (t2 종료 후): " + t1.getState()); // RUNNABLE
    }
}

 

notify() 메서드가 실행되었다고 해서 바로 WAIT -> RUNNABLE 상태가 되는 것이 아니라 

notify() 를 호출한 스레드(t2)가 synchronized 블록을 빠져나가야 바뀐다.

그전에 상태를 출력하면 WAIT -> BLOCKED 상태로 보일 수가 있다.

 

 

6. 스레드 인터럽트 ( interrupt() )

class Interrupt implements Runnable {
    public void run() {
        try {
            System.out.println("실행 중");
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            System.out.println("인터럽트 발생");
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Interrupt());
        t1.start();
        Thread.sleep(2000);
        t1.interrupt(); // 2초 후 인터럽트 요청
    }
}

 

출처

https://velog.io/@wnajsldkf/Java%EC%9D%98-Thread%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

https://adjh54.tistory.com/167

'JAVA' 카테고리의 다른 글

[JAVA] ORM 이란?  (0) 2025.05.31
[JAVA] JDBC 정리  (0) 2025.05.27
[JAVA] 컬렉션(Collection) 정리 / Map  (0) 2025.02.27
[JAVA] 컬렉션(Collection) 정리 / List, Set, Queue  (0) 2025.02.24
[JAVA] 래퍼 클래스(Wrapper Class) 정리  (1) 2025.02.16