1. 소개
이 기사에서는 Java의 핵심 개념인 스레드의 수명 주기에 대해 자세히 설명합니다.
스레드 실행 중 이러한 상태를 더 잘 이해하기 위해 간단한 그림 다이어그램과 실용적인 코드 스니펫을 사용할 것입니다.
Java의 스레드 이해를 시작하려면 스레드 생성에 대한 이 문서 를 시작하는 것이 좋습니다.
2. 자바의 멀티스레딩
Java 언어에서 멀티스레딩은 Thread 의 핵심 개념에 의해 구동됩니다 . 수명 주기 동안 스레드는 다양한 상태를 거칩니다.
3. Java에서 스레드의 수명 주기
java.lang.Thread 클래스에는 잠재적인 상태를 정의 하는 정적 상태 열거형 이 포함되어 있습니다. 특정 시점 동안 스레드는 다음 상태 중 하나만 있을 수 있습니다.
- NEW – 아직 실행을 시작하지 않은 새로 생성된 스레드
- RUNNABLE – 실행 중이거나 실행할 준비가 되었지만 리소스 할당을 기다리고 있습니다.
- BLOCKED - 동기화된 블록/방법에 들어가거나 다시 들어가기 위해 모니터 잠금을 획득하기 위해 대기 중입니다.
- WAITING – 시간 제한 없이 다른 스레드가 특정 작업을 수행하기를 기다
- TIMED_WAITING - 지정된 기간 동안 다른 스레드가 특정 작업을 수행하기를 기다
- TERMINATED – 실행을 완료했습니다.
이러한 모든 상태는 위의 다이어그램에서 다룹니다. 이제 이들 각각에 대해 자세히 설명하겠습니다.
3.1. 새로운
NEW Thread ( 또는 Born Thread )는 생성되었지만 아직 시작되지 않은 스레드입니다. start() 메서드 를 사용하여 시작할 때까지 이 상태를 유지합니다 .
다음 코드 스니펫은 NEW 상태인 새로 생성된 스레드를 보여줍니다.
Runnable runnable = new NewState();
Thread t = new Thread(runnable);
System.out.println(t.getState());
언급된 스레드를 시작하지 않았 으므로 t.getState() 메서드 는 다음을 인쇄합니다.
NEW
3.2. 실행 가능
새 스레드를 만들고 그것에 대해 start() 메서드를 호출하면 스레드가 NEW 에서 RUNNABLE 상태로 이동됩니다. 이 상태의 스레드는 실행 중이거나 실행할 준비가 되어 있지만 시스템에서 리소스 할당을 기다리고 있습니다.
다중 스레드 환경에서 Thread-Scheduler(JVM의 일부)는 각 스레드에 고정된 시간을 할당합니다. 따라서 특정 시간 동안 실행된 다음 다른 RUNNABLE 스레드에 제어권을 양도합니다.
예를 들어 이전 코드에 t.start() 메서드를 추가하고 현재 상태에 액세스해 보겠습니다.
Runnable runnable = new NewState();
Thread t = new Thread(runnable);
t.start();
System.out.println(t.getState());
이 코드는 출력을 다음과 같이 반환 할 가능성이 높습니다 .
RUNNABLE
이 예에서 컨트롤이 t.getState() 에 도달할 때까지 항상 RUNNABLE 상태 에 있다는 보장은 없습니다.
Thread-Scheduler 에 의해 즉시 예약되어 실행이 완료될 수 있습니다. 이러한 경우 다른 출력을 얻을 수 있습니다.
3.3. 막힌
스레드는 현재 실행할 수 없을 때 BLOCKED 상태입니다. 모니터 잠금을 기다리고 다른 스레드에 의해 잠긴 코드 섹션에 액세스하려고 할 때 이 상태에 들어갑니다.
이 상태를 재현해 보겠습니다.
public class BlockedState {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new DemoBlockedRunnable());
Thread t2 = new Thread(new DemoBlockedRunnable());
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println(t2.getState());
System.exit(0);
}
}
class DemoBlockedRunnable implements Runnable {
@Override
public void run() {
commonResource();
}
public static synchronized void commonResource() {
while(true) {
// Infinite loop to mimic heavy processing
// 't1' won't leave this method
// when 't2' try to enter this
}
}
}
이 코드에서:
- 우리는 t1 과 t2 라는 두 개의 서로 다른 스레드를 만들었습니다.
- t1 은 동기화된 commonResource() 메서드 를 시작하고 입력합니다 . 이는 하나의 스레드만 액세스할 수 있음을 의미합니다. 이 메서드에 액세스하려는 다른 모든 후속 스레드는 현재 스레드가 처리를 완료할 때까지 추가 실행에서 차단됩니다.
- t1 이 이 메서드에 들어가면 무한 while 루프에 유지됩니다. 이것은 다른 모든 스레드가 이 메서드에 들어갈 수 없도록 무거운 처리를 모방하기 위한 것입니다.
- 이제 t2 를 시작하면 t1 이 이미 액세스하고 있는 commonResource() 메서드 에 들어가려고 시도 하므로 t2 는 BLOCKED 상태 로 유지됩니다.
이 상태에서 t2.getState() 를 호출 하고 다음과 같은 출력을 얻습니다.
BLOCKED
3.4. 대기 중
스레드는 다른 스레드가 특정 작업을 수행하기를 기다리고 있을 때 WAITING 상태에 있습니다. JavaDocs에 따르면 모든 스레드는 다음 세 가지 메서드 중 하나를 호출하여 이 상태에 들어갈 수 있습니다.
- 객체.대기()
- thread.join() 또는
- LockSupport.park()
wait() 및 join() 에서는 해당 시나리오가 다음 섹션에서 다루므로 제한 시간을 정의하지 않습니다.
wait() , notify() 및 notifyAll() 사용에 대해 자세히 설명 하는 별도의 사용방법(예제) 가 있습니다 .
지금은 이 상태를 재현해 보겠습니다.
public class WaitingState implements Runnable {
public static Thread t1;
public static void main(String[] args) {
t1 = new Thread(new WaitingState());
t1.start();
}
public void run() {
Thread t2 = new Thread(new DemoWaitingStateRunnable());
t2.start();
try {
t2.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
class DemoWaitingStateRunnable implements Runnable {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
System.out.println(WaitingState.t1.getState());
}
}
여기서 우리가 무엇을 하는지 토론해 봅시다:
- 우리는 t1 을 만들고 시작했습니다
- t1 은 t2 를 생성 하고 시작합니다.
- t2 의 처리가 계속되는 동안 t2.join() 을 호출 하면 t2 가 실행을 완료 할 때까지 t1 이 대기 상태 가 됩니다.
- t1 은 t2 가 완료 되기를 기다리고 있으므로 t2 에서 t1.getState() 를 호출 합니다.
여기서 출력은 예상대로 다음과 같습니다.
WAITING
3.5. 시간 초과 대기
지정된 시간 내에 다른 스레드가 특정 작업을 수행하기를 기다리는 스레드는 TIMED_WAITING 상태에 있습니다.
JavaDocs에 따르면 스레드를 TIMED_WAITING 상태로 전환하는 방법에는 5가지가 있습니다.
- thread.sleep(긴 밀리초)
- wait(int timeout) 또는 wait(int timeout, int nanos)
- thread.join(긴 밀리초 )
- LockSupport.parkNanos
- LockSupport.parkUntil
Java에서 wait() 와 sleep() 의 차이점에 대해 자세히 알아보려면 여기에서 이 전용 기사를 살펴보십시오 .
지금은 이 상태를 빠르게 재현해 보겠습니다.
public class TimedWaitingState {
public static void main(String[] args) throws InterruptedException {
DemoTimeWaitingRunnable runnable= new DemoTimeWaitingRunnable();
Thread t1 = new Thread(runnable);
t1.start();
// The following sleep will give enough time for ThreadScheduler
// to start processing of thread t1
Thread.sleep(1000);
System.out.println(t1.getState());
}
}
class DemoTimeWaitingRunnable implements Runnable {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
여기에서 우리는 5초의 시간 초과 기간으로 휴면 상태로 들어가는 스레드 t1 을 생성하고 시작했습니다. 출력은 다음과 같습니다.
TIMED_WAITING
3.6. 종료됨
죽은 스레드의 상태입니다. 실행이 완료되었거나 비정상적으로 종료 된 경우 TERMINATED 상태입니다.
스레드를 중지하는 다양한 방법을 설명 하는 전용 문서 가 있습니다.
다음 예에서 이 상태를 달성해 보겠습니다.
public class TerminatedState implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new TerminatedState());
t1.start();
// The following sleep method will give enough time for
// thread t1 to complete
Thread.sleep(1000);
System.out.println(t1.getState());
}
@Override
public void run() {
// No processing in this block
}
}
여기에서 스레드 t1 을 시작하는 동안 바로 다음 명령문 Thread.sleep(1000) 은 t1 이 완료 될 수 있는 충분한 시간을 제공하므로 이 프로그램은 다음과 같은 출력을 제공합니다.
TERMINATED
스레드 상태 외에도 isAlive() 메서드를 확인하여 스레드가 활성 상태인지 여부를 확인할 수 있습니다. 예를 들어, 이 스레드에서 isAlive() 메서드를 호출하면:
Assert.assertFalse(t1.isAlive());
거짓 을 반환합니다 . 간단히 말해서 스레드는 시작되었고 아직 죽지 않은 경우에만 활성 상태입니다.
4. 결론
이 사용방법(예제)에서는 Java에서 스레드의 수명 주기에 대해 배웠습니다. Thread.State 열거형 으로 정의된 6가지 상태를 모두 살펴보고 빠른 예제로 재현했습니다.
코드 스니펫은 거의 모든 시스템에서 동일한 출력을 제공하지만 일부 예외적인 경우 스레드 스케줄러의 정확한 동작을 결정할 수 없기 때문에 일부 다른 출력을 얻을 수 있습니다.
그리고 항상 그렇듯이 여기에서 사용된 코드 스니펫은 GitHub에서 사용할 수 있습니다 .