들어가기 전

이번 포스팅에서는 스레드의 상태와 생명주기에 대해서 알아보겠습니다.

해당 내용을 알아보면서 "사용자 수준 스레드""커널 수준 스레드"라는 단어가 나오는데 해당 단어들이 무엇을 의미하는지 아래 포스팅을 통해 알게 되고 난 뒤에 이번 내용을 읽으시는 것을 추천드립니다.

 

https://hoestory.tistory.com/81

 

[Java] 멀티 스레드 모델에 대해서

들어가기 전 이번 포스팅에서는 멀티스레드 모델에 대해 알아보겠습니다. 스레드 모델에는 일대일, 일대다, 다대다가 있습니다. 프로세스와 스레드의 차이점에 대해 궁금하신 분은 아래 포스

hoestory.tistory.com

 

먼저 스레드의 상태에 무엇이 있는지  알아보고 스레드의 생명주기에 대해서 알아보겠습니다. 스레드의 상태에서는 간략하게 무슨 상태가 있고 그 상태값이 무엇을 의미하는지와 어떤 행위를 했을 때 그 상태값이 되는지에 대해 알아보겠습니다.
상세한 내용은 생명주기에서 설명하겠습니다.

 

스레드의 상태

  • 생성
  • 실행대기
  • 일시 정지
  • 종료

 

생성(NEW)

사용자 수준 스레드를 생성을 하고 아직 시작되지 않은 상태입니다.

 

public class ThreadTest {

	@Test
	@DisplayName("스레드를 생성을 하면 NEW 상태값을 가진다.")
	void createThreadStateNew() {
		//given && when
		Thread thread = new Thread();

		//then
		Assertions.assertThat(thread.getState()).isEqualTo(State.NEW);
	}
}

 

실행대기(Runnable)

 

실행 중이거나 실행 가능한 스레드의 상태를 의미합니다.

 

public class BlogThread {

	@Test
	@DisplayName("스레드를 실행하면 Runnable 상태값을 가진다.")
	void startThreadStateRunnable() {
		//given && when
		Thread thread = new Thread(() -> {
			System.out.println("스레드 실행");
		});
		thread.start();

		//then
		Assertions.assertThat(thread.getState()).isEqualTo(State.RUNNABLE);
	}
}

 

 

일시 정지

일시 정지 상태값은 3가지로 분류할 수 있습니다.

  • Timed Wait : 대기 시간이 지정된 스레드로서 다른 스레드가  특정 작업을 수행하기를 기다립니다.
  • Wait : 대기 중인 스레드 상태로서 다른 스레드가  특정 작업을 수행하기를 기다립니다.
  • Blocked : 모니터 락이 해제될 때까지 기다리며 차단됩니다.

 

Timed Wait

 

public class ThreadTest {

	@DisplayName("시간을 지정해서 스레드를 일시적으로 멈추면 TimeWait 상태값을 가진다. - sleep")
	@Test
	void timeStopUseMethodSleepThreadStateTimeWait() throws InterruptedException {
		//given
		Thread thread = new Thread(() -> {
			System.out.println("스레드 실행");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
		});
		thread.start();
		Thread.sleep(1000);

		Assertions.assertThat(thread.getState()).isEqualTo(State.TIMED_WAITING);
	}
    
    	@DisplayName("시간을 지정해서 스레드를 일시적으로 멈추면 TimeWait 상태값을 가진다.- wait")
	@Test
	void timeStopUseMethodWaitThreadStateTimeWait() throws InterruptedException {
		//given
		Object lock = new Object();
		Thread thread = new Thread(() -> {
			System.out.println("스레드 실행");
			try {
				synchronized (lock) {
					lock.wait(1000);
				}
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
		});
		thread.start();
		Thread.sleep(1000);
		
		Assertions.assertThat(thread.getState()).isEqualTo(State.TIMED_WAITING);
	}
}

 

  • sleep와 wait 메서드에 시간을 지정을 하면 지정한 시간만큼 해당 스레드는 대기를 합니다. 일정 시간 동안 대기를 하기 때문에 TIMED_WAITING 상태가 됩니다.

 

 

Wait

 

public class ThreadTest {

	@DisplayName("Object 메서드인 wait 사용할때 시간을 지정하지 않으면 Wait 상태값을 가진다.")
	@Test
	void useWaitMethodNotTimeThreadStateWait() throws InterruptedException {
		//given
		Object lock = new Object();
		Thread thread = new Thread(() -> {
			System.out.println("스레드 실행");
			try {
				synchronized (lock) {
					lock.wait();
				}
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
		});
		thread.start();
		Thread.sleep(1000);
		Assertions.assertThat(thread.getState()).isEqualTo(State.WAITING);
	}
}

 

  • wait 메서드에 시간을 지정하지 않고 사용할 경우 WATING 상태가 됩니다.

 

Blocked

 

public class ThreadTest {

	@DisplayName("임계영역이 진입 하려고 할 경우 BLOCKED 상태값을 가진다.")
	@Test
	void criticalSectionThreadStateBlocked() throws InterruptedException {
		Object lock = new Object();

		Thread thread = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (lock) {
					while (true) {
					}
				}
			}
		});
		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (lock) {
					System.out.println("락을 획득하려고 함");
				}
			}
		});
		thread.start();
		Thread.sleep(100);
		thread2.start();
		Thread.sleep(100);
		Assertions.assertThat(thread.getState()).isEqualTo(State.RUNNABLE);
		Assertions.assertThat(thread2.getState()).isEqualTo(State.BLOCKED);
	}
}

 

  • 어떤 하나의 스레드가 접근하고 있는 자원에 다른 스레드가 접근하려고 할 때 BLOCKED 상태값을 가집니다. 즉 다른 스레드가 임계영역에 접근을 하게 되면 접근하려는 스레드는 BLOCKED 상태값이 됩니다.
  • 임계영역 : 한 순간 반드시 하나의 프로세스만  진입해야 하는데 프로그램에서 임계 자원을 이용하는 부분으로 공유 자원의 독점을 보장하는 코드 영역을 의미합니다.

종료(Terminated)

스레드가 종료된 상태입니다.

 

public class BlogThread {

	public static void main(String[] args) throws InterruptedException {
		Thread thread = new Thread(() -> {
		});
		thread.start();

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

 

 

지금까지 스레드의 상태에 대해 알아보았습니다. 이제 스레드의 생명주기에 대해서 알아보겠습니다.

 

스레드의 생명주기

 

 

 

NEW(new MyThread())

스레드를 초기 생성 시 갖는 상태값입니다. 애플리케이션에서 생성된 스레드이기 때문에 해당 스레드는 사용자 수준 스레드입니다.

 

NEW -> Runnable

생성된 사용자 수준 스레드를 start 메서드를 사용하면 실행 대기상태로 됩니다.

이때 start 메서드를 통해 사용자 수준 스레드에서 커널 수준 스레드로 전환이 됩니다.

 

Runnable -> Running

실행 대기 상태에서 우선순위를 가진 스레드를 스케줄링을 통해 실행 상태로 변경합니다.

즉 스레드에서 제공하는 start 메서드를 실행한다고 해서 바로 실행 상태로 되는 것이 아닙니다.

스케줄링을 통해 실행되어야 할 커널 수준 스레드에서 사용자 수준 스레드의 run 메서드를 호출하여 실행 대기 상태에서 실행 상태로 변경이 되면서 스레드를 실행시킵니다.

 

Running -> Timed Waiting -> Runnable -> Running

스레드가 실행되다가 sleep(시간), wait(시간), join(시간) 메서드로 인해 일정 시간 동안 대기하고 있습니다.

지정한 시간이 지나면 다시 실행 가능한 상태로 변경이 됩니다.

실행 가능한 상태에서 스케줄링으로 인해 다시 실행 상태로 변경됩니다.

 

Running -> Waiting -> Runnable -> Running

스레드가 실행이 되다가 join, wait 메서드로 인해 정지상태가 됩니다.

이때 위에서 설명한 Timed Waiting과 달리 기본 Waiting 상태시간을 지정하지 않기 때문에 정지 상태를 해제하고 싶다면 notify, notifyAll 메서드를 사용하여 실행 가능한 상태로 변경할 수 있습니다.

해당 메서드가 호출 안되면 정지상태를 유지하고 있습니다.

실행 가능한 상태변경이 되고 스케줄링으로 인해 다시 실행상태로 변경이 됩니다.

 

 

 

Running -> Blocked -> Runnable -> Running

스레드가 임계영역에 접근하려다가 락을 획득하지 못하고 대기 중인 상태입니다.

락을 획득하게 되면 Blocked 상태에서 실행가능한 상태로 변경이 됩니다.

실행 가능한 상태 변경이 되고 스케줄링으로 인해 다시 실행상태로 변경이 됩니다.

 

Running -> Terminated

예외가 발생하거나 커널 수준 스레드에서 실행한 사용자 수준 스레드의 run 메서드가 정상적으로 실행이 되고 스레드가 끝나면 스레드는 종료상태가 됩니다.