스레드 기본 정보
package thread.control;
import thread.start.HelloRunnable;
import static util.MyLogger.log;
public class ThreadInfoMain {
public static void main(String[] args) {
// main 스레드
Thread mainThread = Thread.currentThread();
log("mainThread = " + mainThread);
log("mainThread.threadId()=" + mainThread.threadId());
log("mainThread.getName()=" + mainThread.getName());
log("mainThread.getPriority()=" + mainThread.getPriority());
log("mainThread.getThreadGroup()=" + mainThread.getThreadGroup());
log("mainThread.getState()=" + mainThread.getState());
// myThread 스레드
Thread myThread = new Thread(new HelloRunnable(), "myThread");
log("myThread = " + myThread);
log("myThread.threadId()=" + myThread.threadId());
log("myThread.getName()=" + myThread.getName());
log("myThread.getPriority()=" + myThread.getPriority());
log("myThread.getThreadGroup()=" + myThread.getThreadGroup());
log("myThread.getState()=" + myThread.getState());
}
}
1. 스레드 생성
: Runnable 인터페이스 구현체와 스레드 이름 전달
Thread myThread = new Thread(new HelloRunnable(), "myThread");
2. 스레드 객체 정보
: Thread 클래스의 toString()은 스레드 ID, 스레드 이름, 우선순위, 스레드 그룹을 포함하는 문자열을 반환
log("myThread = " + myThread);
3. 스레드 ID
: 스레드 고유 식별자를 반환하는 메서드로 JVM 내의 각 스레드에 대해 유일
: 스레드가 생성될 때 할당되며 직접 지정 불가능
log("myThread.threadId() = " + myThread.threadId());
4. 스레드 이름
: 스레드 ID는 중복되지 않지만 이름은 중복 가능
log("myThread.getName() = " + myThread.getName());
5. 스레드 우선순위
: 우선순위는 1(가장 낮음) ~ 10(가장 높음)까지의 값으로 설정
: 기본값은 5
: setPriority()로 우선순위 변경 가능 but 실제 실행 순서는 JVM, OS에 따라 달라질 수 있음
log("myThread.getPriority() = " + myThread.getPriority());
6. 스레드 그룹
: 스레드 그룹은 스레드를 그룹화해 관리하는 기능 제공
: 기본적으로 모든 스레드는 부모 스레드와 동일한 스레드 그룹에 속함
log("myThread.getThreadGroup() = " + myThread.getThreadGroup());
7. 스레드 상태
: Thread.State 열거형에 정의된 상태 중 하나
> NEW : 스레드가 아직 시작되지 않은 상태
> RUNNABLE : 스레드가 실행 중이거나 실행될 준비가 된 상태
> BLOCKED : 스레드가 동기화 락을 기다리는 상태
> WAITING : 스레드가 다른 스레드의 특정 작업이 완료되기를 기다리는 상태
> TIMED_WAITING : 일정 시간 동안 기다리는 상태
> TERMINATED : 스레드가 실행을 마친 상태
* 실행 결과의 main 스레드는 실행 중이기 때문에 RUNNABLE, myThread는 생성 후 아직 시작하지 않았기에 NEW 상태
스레드의 생명 주기 - 설명

1. New(새로운 상태)
> 스레드가 생성되고 아직 시작되지 않은 상태
> Thread 객체가 생성되지만, start()가 호출되지 않은 상태
> ex) Thread thread = new Thread(runnable);
2. Runnable(실행 가능 상태)
> 스레드가 실행될 준비가 된 상태로 실제 CPU에서 실행될 수 있음
> start() 호출 시 스레드는 이 상태로 들어감
> Runnable 상태에 있는 모든 스레드가 동시에 실행되는 것은 아님. OS의 스케줄러가 각 스레드에 CPU 시간을 할당해 실행하기에 Runnable 상태에 있는 스레드는 스케줄러의 실행 대기열에 포함되어 있다가 차례로 CPU에서 실행됨
> OS의 스케줄러 실행 대기열에 있든, CPU에서 실제 실행되고 있든 모두 RUNNABLE 상태로 자바에서는 둘을 구분할 수 없음
> 보통은 실행 상태라고 부름
3. Blocked(차단 상태)
> 스레드가 다른 스레드에 의해 동기화 락을 얻기 위해 기다리는 상태
> ex) synchronized(lock) {...} 코드 블록에 진입하려고 할 때, 다른 스레드가 이미 lock의 락을 가지고 있는 경우
4. Waiting(대기 상태)
> 스레드가 다른 스레드의 특정 작업이 완료되기를 무기한 기다리는 상태
> wait(), join() 메서드 호출 시 이 상태가 됨
> 스레드는 다른 스레드가 notify(), notifyAll()을 호출하거나 join()이 완료될 때까지 기다림
5. Timed Waiting(시간 제한 대기 상태)
> 스레드가 특정 시간 동안 다른 스레드의 작업이 완료되기를 기다리는 상태
> sleep(long millis), wait(long timeout), join(long millis) 호출 시 이 상태가 됨
> 주어진 시간이 경과하거나 다른 스레드가 해당 스레드를 깨우면 이 상태에서 벗어남
6. Terminated(종료 상태)
> 스레드 실행이 완료된 상태
> 스레드가 정상적으로 종료되거나 예외가 발생해 종료된 경우 이 상태가 됨
> 한 번 종료되면 다시 시작할 수 없음
자바 스레드의 상태 전이 과정
1. New -> Runnable : start() 호출
2. Runnable -> Blocked/Waiting/Timed Waiting : 락읕 얻지 못하거나, wait()나 sleep() 호출
3. Blocked/Waiting/Timed Waiting -> Runnable : 락을 얻거나 기다림이 완료된 경우
4. Runnable -> Terminated : run() 메서드 완료 시
스레드의 생명 주기 - 코드
package thread.control;
import static util.MyLogger.log;
public class ThreadStateMain {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRunnable(), "myThread");
log("myThread.state1 = " + thread.getState());
log("myThread.start()");
thread.start();
Thread.sleep(1000);
log("myThread.state3 = " + thread.getState());
Thread.sleep(4000);
log("myThread.state5 = " + thread.getState());
log("end");
}
static class MyRunnable implements Runnable {
@Override
public void run() {
try {
log("start");
log("myThread.state2 = " + Thread.currentThread().getState());
log("sleep() start");
Thread.sleep(3000);
log("sleep() end");
log("myThread.state4 = " + Thread.currentThread().getState());
log("end");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
실행 상태 확인

1. state1 = NEW
> 메인 스레드를 통해 myThread 객체 생성
2. state2 = RUNNABLE
> myThread.start() 호출로 실행 상태로 만듬
> 실행 상태가 너무 빨리 지나가기에 메인 스레드에서는 myThread 상태 확인이 어려워 myThread에서 실행 중인 자신의 상태를 확인함
3. state3 = TIMED_WAITING
> sleep() 호출로 3초간 대기하며 TIMED_WAITING 상태로 만듬
> 이때 메인스레드가 1초간 대기 후 mainThread의 상태 확인
4. state4 = RUNNABLE
> myThread는 3초 대기 후 다시 RUNNABLE 상태가 됨
5. state5 = TERMINATED
> myThread는 run() 메서드 종료 후 TERMINATED 상태가 됨
체크 예외 재정의
Runnable 인터페이스는 다음과 같이 정의되어 있다.
public interface Runnable {
void run();
}
자바에서 메서드를 재정의 할 때, 재정의 메서드가 지켜야할 예외야 관련된 규칙이 있다.
1. 체크 예외
> 부모 메서드가 체크 예외를 던지지 않는 경우, 재정의된 자식 메서드도 체크 예외를 던질 수 없다.
> 자식 메서드는 부모 메서드가 던질 수 있는 체크 예외의 하위 타입만 던질 수 있다.
2. 언체크(런타임) 예외
> 예외 처리를 강제하지 않으므로 상관없이 던질 수 있다.

-> Runnable의 run은 아무런 체크 예외를 던지고 있지 않으므로 재정의 메서드도 체크 예외를 밖으로 던질 수 없음
안전한 예외 처리
체크 예외를 run()에서 던질 수 없게 강제함으로 개발자는 반드시 체크 예외를 try-catch 블록 내에서 처리하게 된다.
이는 예외 발생 시 예외가 적절히 처리되지 않아 프로그램이 비정상 종료되는 상황을 방지할 수 있다.
특히 멀티스레딩 환경에서 예외 처리를 강제함으로 스레드의 안정성과 일관성을 유지할 수 있다.
join 사용
JoinMainV1.java
1 ~ 50까지 합을 계산하는 스레드1과 51 ~ 100까지 합을 계산하는 스레드2로 1부터 100까지 합을 빠르게 구해보자.
package thread.control.join;
import static util.MyLogger.log;
import static util.ThreadUtils.sleep;
public class JoinMainV1 {
public static void main(String[] args) {
log("Start");
SumTask sumTask1 = new SumTask(1, 50);
SumTask sumTask2 = new SumTask(51, 100);
Thread thread1 = new Thread(sumTask1, "thread-1");
Thread thread2 = new Thread(sumTask2, "thread-1");
thread1.start();
thread2.start();
log("task1 result : " + sumTask1.result);
log("task2 result : " + sumTask2.result);
int sumAll = sumTask1.result + sumTask2.result;
log("sumAll : " + sumAll);
log("End");
}
static class SumTask implements Runnable {
int startValue;
int endValue;
int result = 0;
public SumTask(int startValue, int endValue) {
this.startValue = startValue;
this.endValue = endValue;
}
@Override
public void run() {
log("작업 시작");
sleep(2000);
int sum = 0;
for(int i=startValue; i<=endValue; i++) {
sum += i;
}
result = sum;
log("작업 완료 result : " + result);
}
}
}
실행 결과
-> main 스레드는 thread1, 2를 실행하고 바로 자신의 다음 코드를 실행함
-> 예상 결과와 다름.. thread1, 2가 종료된 후 main 스레드를 마지막에 종료하려면?
JoinMainV2.java
package thread.control.join;
import static util.MyLogger.log;
import static util.ThreadUtils.sleep;
public class JoinMainV2 {
public static void main(String[] args) throws InterruptedException {
log("Start");
SumTask sumTask1 = new SumTask(1, 50);
SumTask sumTask2 = new SumTask(51, 100);
Thread thread1 = new Thread(sumTask1, "thread-1");
Thread thread2 = new Thread(sumTask2, "thread-1");
thread1.start();
thread2.start();
// 스레드가 종료될 때까지 대기
log("join() - main 스레드가 thread1, thread2 종료까지 대기");
thread1.join();
thread2.join();
log("join() - main 스레드 대기 완료");
log("task1 result : " + sumTask1.result);
log("task2 result : " + sumTask2.result);
int sumAll = sumTask1.result + sumTask2.result;
log("sumAll : " + sumAll);
log("End");
}
static class SumTask implements Runnable {
int startValue;
int endValue;
int result = 0;
public SumTask(int startValue, int endValue) {
this.startValue = startValue;
this.endValue = endValue;
}
@Override
public void run() {
log("작업 시작");
sleep(2000);
int sum = 0;
for(int i=startValue; i<=endValue; i++) {
sum += i;
}
result = sum;
log("작업 완료 result : " + result);
}
}
}
실행 결과
-> 정확히 5050이 계산됨
join 메서드 동작 확인

main 스레드에서 다음 코드를 실행하면, thread-1 & thread-2가 종료될 때 까지 기다린다.
thread1.join();
thread2.join();
Waiting(대기 상태)
: 스레드가 다른 스레드의 특정 작업이 완료되기를 무기한 기다리는 상태
: join()을 호출하는 스레드는 대상 스레드가 TERMINATED가 될 때 까지 대기함, 호출 스레드는 WAITING 상태
: 대상 스레드가 TERMINATED 상태가 되면 호출 스레드는 다시 RUNNABLE 상태가 되며 다음 코드 수행
join의 두 가지 메서드
join() : 호출 스레드는 대상 스레드가 완료될 때까지 무한정 대기
join(ms) : 호출 스레드는 특정 시간 만큼만 대기 -> 대기하는 동안 TIMED_WAITING 상태가 됨
'인프런 > 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성' 카테고리의 다른 글
| [인프런] 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성 / 6. 동기화 - synchronized (0) | 2025.03.13 |
|---|---|
| [인프런] 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성 / 5. 메모리 가시성 (0) | 2025.02.26 |
| [인프런] 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성 / 4. 스레드 제어와 생명 주기 2 (0) | 2025.02.14 |
| [인프런] 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성 / 2. 스레드 생성과 실행 (0) | 2025.01.15 |
| [인프런] 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성 / 1. 멀티태스킹과 멀티프로세싱 (1) | 2025.01.06 |