[Java]Thread 특성
- Thread의 스케줄링과 쓰레드의 우선순위 컨트롤
다수의 쓰레드가 생성될 수 있기에, JVM(자바 가상 머신)은 쓰레드의 실행을 스케줄링(컨트롤) 해야 합니다. 다음과 같은 기본적인 알고리즘을 따라 스케줄링합니다.
- 우선순위가 높은 쓰레드의 실행을 우선한다.
- 동일한 운선순위 쓰레드가 둘 이상 존재할 때 CPU의 할당시간을 분배해서 실행한다.
자바의 쓰레드는 우선 순위가 할당되는데, 이는 우선적으로 실행되어야 하는 쓰레드의 순위를 의미합니다. 가장 높은 우선순위는 정수 10, 가장 낮은 우선순위는 정수 1로 표현합니다.
참고사항 : 자바가 언어 차원에서 쓰레드를 지원하고는 있지만, 쓰레드는 특성상 운영체제에 상당히( 윈도우10, 리눅스 등) 의존적입니다. 즉 가상 머신이 동작하는 운영체제에따라서 실행의 결과가 달라질 수 있습니다. 자바에서 우선순위 단계를 10단계로 나누지만, 운영체제의 종류에 따라서 10단계가 아닌 7단계, 5단계 등으로 바뀔 수 있습니다. 그렇기 때문에 우선순위를 변경할 때에는 상수로 정의되어 있는 MAX_PRIORITY(가장 높은 우선순위), NORM_PRORITY(중간 우선순위), MIN_PRORITY(가장 낮은 우선순위)를 사용하는 것이 좋습니다.
class MessageSendingThread extends Thread{
String message;
public MessageSendingThread (String str, int n) {
message = str;
setPriority(n); // 쓰레드 우선순의를 변환해주는 메소드
}
public void run() {
for(int i = 0; i<5; i++) {
System.out.println(message + "(" + getPriority()+ ")");
try
{
sleep(1);
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class PriotityTestTwo {
public static void main(String[] args) {
// TODO Auto-generated method stub
MessageSendingThread tr1 = new MessageSendingThread("first", Thread.MAX_PRIORITY); // static 변수로 10을, 최고 우선순의를 뜻함
MessageSendingThread tr2 = new MessageSendingThread("Second", Thread.NORM_PRIORITY); //static 변수로 5를, 중간 우선순의를 뜻한다.
MessageSendingThread tr3 = new MessageSendingThread("Third", Thread.MIN_PRIORITY);//static 변수로 1을, 최저 우선순의 뜻합니다.
tr1.start();
tr2.start();
tr3.start();
/*
* sleep 메소드가 호출되면 쓰레드는 Cpu할당이 불필요한 상황이 되므로 다른 쓰레드에게 할당된 CPU를 양보한다.
*/
}
}
실행결과 :
first(10)
Third(1)
Second(5)
Second(5)
Third(1)
first(10)
Second(5)
Third(1)
first(10)
Second(5)
first(10)
Third(1)
Second(5)
Third(1)
first(10)
-> 위 실행결과를 보면 알 수 있지만 우선순위가 높은 쓰레드에게만 실행 기회를 부여하는 것은 아닙니다. 그 이유는 CPU를 할당받은 쓰레드가 CPU의 할당이 필요치 않은 입출력을 처리하는 상황에 놓였을 때, 해당 쓰레드는 CPU를 차지하지 않고 CPU를 필요로 하는 다른 쓰레드에게 넘깁니다. 따라서 우선순위가 낮은 쓰레드 역시 실행의 가치를 얻을 수 있습니다.
- 쓰레드의 라이프 사이클

쓰레드가 생성되면 위 그림에 보이는 4가지의 상태 중 한가지 상태로 존재하게 됩니다.
- New 상태 : 쓰레드 클래스가 인스턴스화 된 상태입니다. JVM에 의해 관리되는 상태는 아니기 때문에, 운영체제에서는 쓰레드라고 부르지 않지만, Java에서 이 상태부터 쓰레드라고 부릅니다.
- Runnable 상태 : 쓰레드 인스턴스를 대상으로 start 메소드가 호출되면, 해당 쓰레드는 'Runnable 상태'가 됩니다. 이는 모든 실행 준비를 마치고, 스케줄러에 의해서 선택되어 실행될 수 있기만을 기달리는 상태입니다.
- Blocked 상태 : CPU를 다른 쓰레드에게 양보한 상태, Blocked 상태에서는 스케줄러의 선택을 받을 수 없습니다. 다시 선택받을려면, Blcoked 상태에 놓이게 된 원인이 제거되어서 Runnable 상태로 돌아와야 합니다.
- Dead 상태 : run메소드의 실행이 완료되어서 run 메소드를 빠져 나와 해당 쓰레드가 종료된 상태입니다. 쓰레드 실행을 위해 할당 받았던 메모리를 비롯해 각종 쓰레드 관련 정보가 완히 사라진 상태입니다. 다시 Runnable 상태가 될 수 없습니다.
- 쓰레드의 메모리 구성

쓰레드의 가장 큰 역할은 별도의 실행흐름 입니다. 이렇게 별도의 실행흐름을 만들기 위해서는 각각 메모리 공간을 별도로 할당받게 되는데, 그 중에서 스택 영역만 별도로 할당받고 메소드, 힙 영역은 다른 쓰레드와 main 메소드와 공유하게 됩니다. 이 말은 즉슨, (A 쓰레드, B 쓰레드 , main 메소드가 존재한다는 가정하에) A 쓰레드가 만든 인스턴스의 참조 값을(주소 값) 알면 B 쓰레드도 A쓰레드가 만든 인스턴스에 접근이 가능합니다.
class Sum{
int num;
public Sum() {num = 0;}
public void addNum(int n) {num +=n;}
public int getNum() {return num;}
}
class AdderThread extends Thread{
Sum sum;
int start;
int end;
public AdderThread(Sum sum, int s, int e) {
this.sum = sum;
start = s;
end = e;
}
public void run() {
for(int i = start; i <=end; i++) {
sum.addNum(i);
}
}
}
public class ThreadHeapMultAccess {
public static void main(String[] args) {
Sum s = new Sum();
AdderThread a1 = new AdderThread(s,1,50);
AdderThread a2 = new AdderThread(s,51,100);
a1.start();
a2.start();
try {
a1.join();
a2.join();
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(s.getNum());
/*
* 쓰레드와 메인문은 메모리에서 스택영역을 제외하고 힙, 메소드 영역을 공유하기 때문에
* Sum 인스턴스에 같이 접근할 수 있다. 만약, 힙 영역이 독립적으로 할당되었다면,
* 참조 값을 쓰레드에 전달할 수는 있으나, 인스턴스에 접근할 때 문제가 생긴다.(각자 자신에게 할당된 인스턴스를 찾을려고 하기 때문)
*/
}
}
실행결과 : 5050
위 코드를 보면 s 인스턴스의 참조변수를 AdderThread 인스턴스 a1, a2의 생성자로 넘겨줘 참조하고 있습니다. 따라서 두 개의 쓰레드는 힙 영역을 공유하고 있기 때문에 이 처럼 main문에서 생성한 인스턴스에 접근이 가능한 것 입니다.