본문 바로가기

자바공부

자바 스레드 생명 주기와 스케줄링, 명품자바프로그래밍13장

728x90
반응형

* 스레드는 하나의 생명체이다.

 

* 스레드는 태어나서 실행하고 종료하는 등 생명 주기(life cycle)를 가진다. 그리고 생명 주기 동안 여러 상태의 변이를 거친다.

스레드의 상태는 총 6가지이며 JVM에 의해 관리된다.

1) NEW

스레드가 생성되었지만 아직 실행할 준비가 되지 않은 상태이다.

start() 메소드가 호출되면 RUNNABLE 상태가 된다.

2) RUNNABLE

스레드가 현재 실행되고 있거나 실행 준비되어 스케줄링을 기다리는 상태이다.

3) WAITING

스레드가 어떤 Object 타입의 객체 a에 대해 a.wait()을 호출하고 무한 대기하면서 다른 스레드가 a.notify(), a.notifyAll()을 불러주기를 기다리고 있는 상태이다. 스레드 동기화를 위해 사용된다.

4) TIMED_WAITING

스레드가 sleep(int n)을 호출하여 n밀리초 동안 잠을 자고 있는 상태이다.

5) BLOCK

스레드가 I/O 작업을 요청하여 I/O 작업이 완료되기를 기다리는 상태이다. 응용프로그램이 I/O작업을 실행하면 JVM이 자동으로 현재 스레드를 BLOCK 상태로 만든다.

6) TERMINATED

스레드가 종료한 상태이다. 더 이상 다른 상태로 변이할 수 없다.

스레드의 상태 변이도

 

* 스레드의 일생

응용프로그램의 new Thread()에 의해 스레드 객체가 생성되면 JVM은 스레드 관리 정보(TCB)를 생성하여 관리한다.

이 때 생성된 스레드는 NEW 상태가 된다. JVM은 NEW 상태의 스레드는 절대로 스케줄링하지 않기 때문에 이 스레드는 현재 상태로서는 실행될 수 없는 상태이다.

 

* Thread 클래스의 start() 메소드가 호출되면 스레드는 비로소 스케줄링되어 실행 가능한 상태인 RUNNABLE(준비) 상태가 된다.

JVM은 현재 만들어진 RUNNABLE(준비) 상태에 있는 스레드 중에서 하나의 스레드를 선택하고 실행시킨다.

처음으로 스케줄링되는 스레드는 run() 메소드의 첫 라인부터 실행을 시작한다.

JVM은 스케줄링 시 우선순위가 높은 스레드를 우선적으로 선택한다.

만일 우선순위가 동일한 스레드가 여러 개 있으면 흔히 말하는 라운드 로빈(round robin), 즉 돌아가면서 선택한다.

 

* 실행 중인 스레드가 일시 중단되는 경우는 여러가지가 있다.

1) 우선 실행 중인 스레드가 I/O(입출력)작업을 하게 되면 JVM에 의해 즉각 중지되며 BLOCK 상태가 된다. BLOCK 상태의 스레드는 입출력 작업이 완료될 때까지 스케줄링되지 않고 계속 대기한다. 입출력 작업이 완료되면 스레드는 자동으로 RUNNABLE(준비) 상태가 된다. 화면 출력이나 키보드 입력, 프린터 출력, 파일 입출력, 네트워크 데이터 송수신 등 모든 종류의 입출력 작업 시 BLOCK 상태가 된다.

2) 실행 중인 스레드가 만일 yield()를 호출하면 다른 스레드가 스케줄링될 수 있도록 양보하겠다는 표시이며, JVM은 즉각적으로 현재 실행 중인 스레드를 RUNNABLE(준비) 상태로 변경하고 다시 스레드 스케줄링을 실시한다. 만일 다른 높은 우선순위의 스레드가 없거나 동일한 우선순위의 다른 스레드가 없으면 이 스레드가 다시 스케줄링된다.

3) 실행 중인 스레드가 sleep(mills)을 호출하면 mills 밀리초 시간만큼 잠을 자게 되므로, JVM은 이 스레드를 TIMED_WAITING 상태로 변경하고 다시 스케줄링을 시작한다. 이 스레드는 mills 및치로 시간만큼 경과한 뒤에 자동으로 깨어나서 RUNNABLE(준비) 상태로 가게 된다.

4) 실행 중인 스레드가 종료하면 TERMINATED 상태로 되며 JVM은 다시 스케줄링을 시작한다. TERMINATED 상태의 스레드는 더 이상 RUNNABLE 상태로 돌아올 수 없기 때문에 다시 실행될 수 없다.

 

* 실행 중인 스레드가 어떤 객체 a의 wait() 메소드를 호출하여 다른 스레드로부터 깨워지기를 기다리는 경우이다. 이때 객체 a를 동기화 객체라고 부른다. 

모든 객체는 java.lang.Object를 상속받으며 wait()메소드는 java.lang.Object 클래스의 멤버이므로 모든 객체가 동기화 객체가 될 수 있다.

첫 번째 스레드가 객체 a를 기다리고 있을 때, 다른 스레드가 a.notify()나 a.notifyAll() 메소드를 호출하게 되면 첫 번째 스레드는 잠에서 깨어나서 RUNNABLE(준비) 상태로 돌아가게 된다.

 

* JVM은 철저히 우선순위를 기반으로 스레드를 스케줄링한다.

동일한 우선순위인 경우에는 라운드 로빈으로 돌아가면서 실행시킨다.

자바 스레드의 우선순위 체계:

1) 최대값(MAX_PRIORITY) = 10

2) 최소값(MIN_PRIORITY) = 1

3) 보통 값(NORMAL_PRIORITY) = 5

 

* 자바 응용프로그램이 실행될 때 처음으로 생성되는 main 스레드 혹은 메인 스레드는 보통 값(5)의 우선순위로 태어나며, 자식 스레드는 부모 스레드와 같은 우선순위 값을 갖고 태어나기 때문에 main 스레드의 모든 자식 스레드는 보통값(5)의 우선순위를 가지고 탄생된다.

그러나 다음 메소드를 이용하여 우선순위를 변경할 수 있다.

void setPriority(int newPriority) // newPriority로 스레드의 우선순위 값 변경

 

* 본래 자바 스레드는 비선점(nonpreemptive) 스케줄링을 한다. 그러므로 원칙적으로 한 번 실행된 스레드가 스스로 양보하거나 종료되는 경우가 아니라면 다른 스레드는 실행될 기회를 가질 수 없다. 

따라서 개발자는 스레드 실행 도중 다른 스레드에게 양보하도록 yield() 메소드를 호출하여야 하는 것이 원칙이다. 

그러나 JVM이 윈도우처럼 멀티스레딩을 지원하는 운영체제상에서 실행되는 경우, JVM은 운영체제의 스레드를 자바 스레드에 매핑시켜 선점(preemptive) 스케줄링이 가능하게 한다.

이런 경우 자바 스레드는 운영체제의 시분할 체계에 의해 자동으로 다른 스레드에게 실행을 양보당하게 되기 때문에 굳이 yield() 메소드를 호출할 필요가 없다.

 

* main()메소드는 JVM에 의해 생성된 main스레드에 의해 호출된다.

main스레드는 JVM이 데몬 스레드가 아닌 일반 스레드로 등록한다.

그러므로 자바 응용프로그램의 main()메소드가 실행되는 순간 2개의 스레드가 존재하는 셈이다.

하나는 main스레드이고 다른 하나는 JVM이 생성한 가비지 컬렉터이다.

반응형