먼저 스레드(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초 후 인터럽트 요청
}
}
출처
'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 |