8.3 스레드의 제어

JAVA PROGRAMMING/JAVA 2010/02/21 21:44 by 킨테리 KinTeL



8.3.1 스레드의 우선권



스레드의 우선권(Priority)을 어떻게 주느냐에 따라서 스레드의 작업 순서가 달라집니다. 작업 순서가 달라진다는 것은 Runnable 상태에서 얼마나 자주 Run 상태가 될 수 있느냐를 말하는 것입니다. 우선권이 높다면 Run 상태가 될 확률이 높겠죠. 우선권이 높다면 다른 스레드들 보다 작업을 빨리 끝낼 수 있습니다. 스레드에 할당할 수 있는 스레드의 우선권 상수는 다음과 같습니다.

▣ Thread 클래스의 스태틱 우선권 상수
◈ public static final int MIN_PRIORITY = 1;
◈ public static final int NORM_PRIORITY = 5;
◈ public static final int MAX_PRIORITY = 10;

▣ 우선권 문제
◈ 어떠한 스레드가 Run 상태를 많이 차지할 것인가의 문제이다.

스레드의 우선권에 대한 값은 Thread 클래스의 public static final 멤버로 정의되어 있습니다. 스레드의 우선권을 셋팅하기 위해서 1부터 10까지의 수를 사용해도 되며, Thread의 스태틱 우선권 상수변수를 사용해도 됩니다. 우선권을 설정하기 위해서는 다음과 같이 Thread의 setPriority() 메서드를 사용하면 됩니다.

▣ 스레드의 상태 설정하기
◈ PriorityThread t = new PriorityThread();
◈ t.setPriority(1);
◈ //t.setPriority(Thread.MIN_PRIORITY);
◈ //우선권이 가장 낮은 상태

◈ t.setPriority(5);
◈ //t.setPriority(Thread.NORM_PRIORITY);
◈ //일반적인 스레드가 갖는 우선권

◈ t.setPriority(10);
◈ //t.setPriority(Thread.MAX_PRIORITY); 
◈ //우선권이 가장 높은 상태

현재 실행중인 스레드의 우선권을 얻고자 한다면 다음과 같이 getPriority()를 사용하면 됩니다.

▣ 스레드에 설정된 우선권 얻어내기
◈ int p = t.getPriority();

▣ 우선권(Priority)
◈ 스레드의 우선권을 설정하기 위해서 setPriority() 메서드를 사용하고, 현재 설정되어 있는 우선권을 얻어내기 위해서 getPriority()를 사용한다.

자! 그럼 이 두 메서드를 이용해서 스레드의 우선권을 변경해 보도록 하겠습니다.

『chap08\PriorityThreadMain.java』
ⓙ───────────────────────────────────────
/**
스레드의 우선권을 테스트하는 예제
**/
class PriorityThread extends Thread {                        
    public void run() {
        int i = 0;
        System.out.print(this.getName()); //스레드의 이름 출력
        System.out.println("[우선권:" + this.getPriority() + "] 시작\t");
        while(i < 10000) {
            i = i + 1;
            try{
                this.sleep(1);
            }catch(Exception e){System.out.println(e);}
        }
        System.out.print(this.getName()); //스레드의 이름 출력
        System.out.println("[우선권:" + this.getPriority() + "] 종료\t");
    }
} //end of PriorityThread class

public class PriorityThreadMain {
   public static void main(String[] args) {
        System.out.println("Main메서드 시작");
        for(int i=1; i<=10; i++){
        //for(int i=Thread.MIN_PRIORITY; i<=Thread.MAX_PRIORITY; i++){
            PriorityThread s = new PriorityThread();
            s.setPriority(i);
            s.start();
        }
        System.out.println("Main메서드종료");
   }//end of main
} //end of PriorityThreadMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap08>javac PriorityThreadMain.java
C:\javasrc\chap08>java PriorityThreadMain
Main메서드 시작
Thread-6[우선권:6] 시작
Thread-7[우선권:7] 시작
Thread-8[우선권:8] 시작
Thread-9[우선권:9] 시작
Thread-5[우선권:5] 시작
Thread-10[우선권:10] 시작
Main메서드종료
Thread-3[우선권:3] 시작
Thread-4[우선권:4] 시작
Thread-1[우선권:1] 시작
Thread-2[우선권:2] 시작
Thread-10[우선권:10] 종료
Thread-8[우선권:8] 종료
Thread-9[우선권:9] 종료
Thread-6[우선권:6] 종료
Thread-7[우선권:7] 종료
Thread-5[우선권:5] 종료
Thread-3[우선권:3] 종료
Thread-4[우선권:4] 종료
Thread-1[우선권:1] 종료
Thread-2[우선권:2] 종료
***/
───────────────────────────────────────ⓑ

이 결과는 컴퓨터의 성능에 따라서 약간씩 달라질 수 있습니다. 보다 정확한 결과를 보고자 한다면 while문의 반복횟수를 더 늘려 보시기 바랍니다. 현재의 작업이 단순하기 때문에 반복문을 많이 돌려야만 좋은 결과를 얻을 수 있습니다.

▣ sleep(1)을 사용한 이유(1은 1/1000초를 의미)
◈ sleep(1)을 사용하면 작업을 약간 지연시키는 효과가 있다. 이것은 단순한 작업 처리에서 우선권이 어떻게 변화하는 지를 눈으로 확인할 수 있게 해준다.

우선권을 다르게 주기 위해서 다음과 같이 10개의 스레드를 생성할 때 단계별로 우선권을 설정한 후 실행시키고 있습니다.

▣ 1부터 10까지 우선권 부여
◈ for(int i=1; i<=10; i++){
◈ //for(int i=Thread.MIN_PRIORITY; i<=Thread.MAX_PRIORITY; i++){
◈        PriorityThread s = new PriorityThread();
◈        s.setPriority(i);
◈        s.start();
◈ }

MIN_PRIORITY와 MAX_PRIORITY까지 모든 우선권을 전부 사용해서 스레드를 생성하고 있습니다. 결과는 MAX_PRIORITY를 가진 Thread-9번이 제일 먼저 작업을 끝내고, Thread-0번이 마지막에 작업을 끝내는 것을 볼 수 있습니다. 작업이 단순하기 때문에 약간의 지연효과를 위해서 sleep(1)에서 1/1000초의 시간동안 대기시키고 있습니다. 이로써 스레드의 우선권을 변경하는 방법에 대해서 알아보았습니다.



8.3.2 NotRunnable 상태 만들기



우선권은 어떤 스레드가 Run 상태를 더 많이 차지할 것인가의 문제입니다. 즉 우선권이 높으면 그만큼 Run 상태에 자주 들어갈 수 있고 작업을 빨리 끝낼 수 있습니다. 이번에는 스레드를 NotRunnable 상태로 보내는 방법에 대해서 배워 보도록 하겠습니다. 스레드가 NotRunnable 상태가 될 수 있는 방법은 다음과 같이 두 가지가 있습니다.

▣ 스레드를 NotRunnable 상태로 만드는 방법
◈ sleep()을 이용해서 일정시간 동안만 대기시키는 방법(자동)
◈ wait()와 notify()를 이용해서 대기와 복귀를 제어하는 방법(수동)

sleep()과 wait()는 스레드를 NotRunnable 상태로 만듭니다. sleep()의 경우는 주어진 시간 만큼만 NotRunnable 상태로 보내며, 시간이 완료되면 자동으로 Runnable 상태로 복귀하게 됩니다. wait()의 경우에는 사용자가 직접 wait()를 호출해서 NotRunnable 상태로 만들며, Runnable 상태로 되돌아오기 위해서는 수동으로 notify()를 호출해 주어야 합니다. sleep()은 자동이며 wait()와 notify()는 수동으로 처리되는 것입니다. sleep()의 경우에는 다음과 같이 대기시킬 시간만 주고 호출하면 됩니다. 

▣ sleep()의 사용
◈ try{
◈         Thread.sleep(1000); //시간의 단위는 1/1000초
◈ }catch(InterruptedException e){e.printStackTrace();}

1초만큼 NotRunnable 상태가 되며 1초가 지난 뒤에는 자동으로 Runnable 상태로 되돌아와서 작업을 재개하게 됩니다. sleep()은 Thread의 스태틱 메서드이기 때문에 프로그램 어디서나 사용할 수 있으며 일시적으로 작업을 중단(대기)시키는 역할을 합니다. 다음은 main()에서 작업을 5초동안 멈추게 하는 예입니다.

 『chap08\NotRunnableMain.java』
ⓙ───────────────────────────────────────
/**
sleep()을 이용한 작업의 일시 중단 - main()에서의 Thread.sleep()
**/
public class NotRunnableMain{
    public static void main(String[] args){
        long current = System.currentTimeMillis();
        System.out.println("프로그램 시작");
        try{
            Thread.sleep(5000);
        }catch(InterruptedException e){e.printStackTrace();}
        System.out.println("프로그램 종료");
        System.out.println("시간: " + (System.currentTimeMillis()-current));
    } //end of main
} //end of NotRunnableMain
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap08>javac NotRunnableMain.java
C:\javasrc\chap08>java NotRunnableMain
프로그램 시작
프로그램 종료
시간: 5000
***/
───────────────────────────────────────ⓑ

main()에도 sleep()을 사용할 수 있으며, 스레드의 run()에서도 사용할 수 있습니다. 그리고 sleep()을 사용할 때 의무적으로 InterruptedException 처리를 해주어야 합니다. 다음은 스레드에서 sleep()을 사용한 예입니다. 

『chap08\NotRunnableThreadMain.java』
ⓙ───────────────────────────────────────
/**
스레드에서 sleep()의 사용
**/
import java.util.*;
class NotRunnableThread extends Thread {
    public void run() {
        int i = 0;
        while(i < 10) {
            System.out.println(i + "회:" + System.currentTimeMillis() + "\t");
            i = i + 1;
            try{
                this.sleep(1000);
            }catch(Exception e){System.out.println(e);}
        }
    }
} //end of NotRunnableThread

public class NotRunnableThreadMain {
   public static void main(String args[] ) {
       NotRunnableThread s = new NotRunnableThread();
       s.start();
   } //end of main
} //end of NotRunnableThread class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap08>javac NotRunnableThreadMain.java
C:\javasrc\chap08>java NotRunnableThreadMain
0회:1087996213815
1회:1087996214815
2회:1087996215815
3회:1087996216815
4회:1087996217815
5회:1087996218815
6회:1087996219815
7회:1087996220815
8회:1087996221815
9회:1087996222815
***/
───────────────────────────────────────ⓑ

일반적으로 스레드는 지속적인 작업을 목적으로 하기 때문에 일정 시간동한 작업을 멈추게 하는 것은 반드시 필요합니다. 하나의 스레드가 CPU를 독점하는 것을 막기 위해서 sleep()은 간단하면서도 효과적인 방법입니다.

Thread를 상속받은 상태에서 sleep()을 사용할 때에는 Thread.sleep()처럼 사용하지 않아도 됩니다. Thread의 멤버이기 때문에 위에서는 다음과 같이 사용하고 있습니다.

▣ Thread를 상속한 경우 sleep()의 사용
◈ this.sleep(1000);

위의 예제에서 run() 메서드 내의 sleep()은 해당 스레드를 일정 시간동안 NotRunnable 상태로 만들어 버립니다. NotRunnable 상태가 되었을 때 다른 스레드가 Runnable 상태에서 CPU의 제어권을 가지게 될 확률이 높아집니다. 보통의 경우에는 지금처럼 하나의 스레드를 제어하는 것이 아니라 여러 개의 스레드를 동시에 제어하면서, sleep()을 이용해서 작업의 로드 밸런싱(Load Balancing)을 하게 됩니다.

▣ public static void sleep(long millis) throws InterruptedException
◈ millis 시간만큼 작업을 멈추게 되며, 시간이 경과했을 때 다시 작업을 재개하게 된다.
◈ millis는 1/1000초 단위를 사용한다.




8.3.3 스레드 죽이기



스레드를 계속 살아서 움직이게 하기 위해서 보통 run() 메서드 내에서 while문을 사용합니다. 조건이 만족하는 한 지속되고 반복된다는 특징 때문에 run() 메서드에서 while문이 자주 등장합니다. 스레드의 종료는 run() 메서드의 종료를 의미하기 때문에 일반적으로 while문의 종료가 곧 스레드의 종료가 되는 경우가 많습니다.

▣ 스레드의 종료
◈ run() 메서드의 종료는 스레드의 종료를 의미한다.
◈ 일반적으로 지속적인 작업을 하기 위해 run() 내에 while문을 포함하고 있으며, 이 while문이 끝나면 스레드가 종료되는 경우가 많다.

사실 스레드에 대해서 조금 아시는 분들은 스레드를 종료하기 위해서 stop()을 사용하지 않느냐라고 말할 것입니다. 물론 자바의 스레드를 종료하기 위해서 다음과 같은 메서드를 제공하고 있지만 이 메서드는 Deprecated 되었습니다.

▣ Deprecated된 Thread의 stop() 메서드
◈ public final void stop() Deprecated
◈ public final void stop(Throwable obj) Deprecated

▣ Deprecated된 메서드
◈ Deprecated된 메서드는 안전하지 못하며 권장하지 않는 구버전의 메서드라는 의미
◈ Java Doc API를 보면 Deprecated된 메서드는 굵은 글씨체로 정확하게 표시하고 있다.

Deprecated 되었다는 말은 이 메서드를 사용하는 것은 안전하지 못하며 권장하지 않는 구버전의 메서드라는 용어입니다. 즉 stop()을 사용하지 말라는 뜻입니다. 그렇다면 스레드를 안전하게 종료할 방법이 있어야 합니다. 보통의 경우 어떠한 방식으로든 run()을 종료함으로써 스레드를 멈추게 하면 됩니다. 일반적으로 다음과 같이 while문의 조건을 제어함으로써 run()을 빠져 나오게 하는 방법을 주로 사용합니다.

▣ run() 메서드의 일반적인 모델(while문의 조건에 따라 run()의 종료를 제어)
◈ public void run(){
◈         while(조건){
◈             //작업
◈         }
◈ }

while문의 조건을 이용한 스레드의 종료를 테스트하는 예제는 다음과 같습니다.

『chap08\TerminateThreadMain.java』
ⓙ───────────────────────────────────────
/**
while문의 조건을 이용한 스레드의 종료를 테스트하는 예
**/
class TerminateThread extends Thread {
    //스레드의 종료를 제어하는 플래그
    private boolean flag = false;
    public void run() {
        int count = 0;
        System.out.println(this.getName() +"시작");
        while(!flag) {
            try {
                //작업
                this.sleep(100);
            } catch(InterruptedException e) {  }
        }
        System.out.println(this.getName() +"종료");
    }
    public void setFlag(boolean flag){
        this.flag = flag;
    }
} //end of TerminateThread class

public class TerminateThreadMain {
    public static void main(String args[])throws Exception{
        System.out.println("작업시작");
        TerminateThread a = new TerminateThread();
        TerminateThread b = new TerminateThread();
        TerminateThread c = new TerminateThread();
        a.start();
        b.start();
        c.start();
        int i;
        System.out.print("종료할 스레드를 입력하시오! A, B, C, M?\n");
        while(true){
            i = System.in.read();
            if(i == 'A'){
                a.setFlag(true);
            }else if(i == 'B'){
                b.setFlag(true);
            }else if(i == 'C'){
                c.setFlag(true);
            }else if(i == 'M'){
                a.setFlag(true);
                b.setFlag(true);
                c.setFlag(true);
                System.out.println("main종료");
                break;
            }
        }
    } //end of main
} //end of TerminateThreadMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap08>javac TerminateThreadMain.java
C:\javasrc\chap08>java TerminateThreadMain
작업시작
종료할 스레드를 입력하시오! A, B, C, M?
Thread-1시작
Thread-2시작
Thread-3시작
A
Thread-1종료
B
Thread-2종료
C
Thread-3종료
M
main종료

C:\javasrc\chap08>java TerminateThreadMain
작업시작
종료할 스레드를 입력하시오! A, B, C, M?
Thread-1시작
Thread-2시작
Thread-3시작
M
main종료
Thread-1종료
Thread-2종료
Thread-3종료
***/
───────────────────────────────────────ⓑ

외부에서 스레드를 제어하기 위해서 스레드에 다음과 같이 boolean형 멤버 변수와 이 멤버 변수를 셋팅하는 멤버 메서드를 두고 있습니다.

▣ 스레드 제어를 위한 flag 설정하기
◈ private boolean flag = false;
◈ //flag가 true로 설정되면 while문이 끝난다.
◈ public void setFlag(boolean flag){
◈        this.flag = flag;
◈ }
 
그리고 스레드를 제어하기 위해서 while문의 조건에 flag를 이용하고 있습니다. 

▣ flag를 이용한 run() 내의 while문 제어
◈ while(!flag) {
◈         //작업
◈ }

flag가 true가 되었을 때 while문은 동작을 멈추게 되며 run()을 빠져 나오게 됩니다. main()에서는 다음과 같이 스레드를 생성한 후 start()를 호출해서 작업을 시작하게 됩니다.

▣ 스레드의 생성과 시작
◈ TerminateThread a = new TerminateThread();
◈ TerminateThread b = new TerminateThread();
◈ TerminateThread c = new TerminateThread();
◈ a.start();
◈ b.start();
◈ c.start();

콘솔에서 스레드를 제어하기 위한 문자를 입력받기 위해서 다음과 같이 System.in을 이용하고 있습니다.

◈ i = System.in.read();

문자는 숫자형식으로 입력받게 되며, 'A', 'B', 'C', 'M'의 입력문자에 따라 각각의 스레드를 제어하고 있습니다. flag의 설정이 true가 되면 해당 스레드는 종료하게 됩니다. 'A'를 입력받으면 스레드 a를, 'B'를 입력받으면 스레드 b를, 'C'를 입력받으면 스레드 c를 종료하게 되며, 'M'을 입력받으면 전체 스레드를 하나씩 멈추게 되며 main()의 while문까지 빠져 나오게 됩니다.

위의 예는 각각의 스레드를 제어하기 위한 수단으로 boolean형 멤버 변수를 이용하게 됩니다. 이러한 제어는 스레드의 종료를 제어하는 일반적인 방법입니다. 하지만 각각의 스레드를 제어해야 한다는 단점이 있습니다. 만약 한번에 모든 스레드를 제어하고자 한다면 스태틱 멤버 변수를 이용해서 종료를 제어하면 됩니다. 다음은 각각의 스레드 제어와 통합적인 스레드 제어를 위해서 일반 멤버 변수와 스태틱 멤버 변수를 이용하는 예입니다.

『chap08\ControlThreadMain.java』
ⓙ───────────────────────────────────────
/**
두개의 조건을 이용한 스레드의 종료
**/
class ControlThread extends Thread {
    //모든 스레드의 종료를 제어하는 플래그
    public static boolean all_exit = false;
    //스레드의 종료를 제어하는 플래그
    private boolean flag = false; 
    public void run() {
        int count = 0;
        System.out.println(this.getName() +"시작");
        //flag나 all_exit 둘 중 하나만 true이면 while문이 끝난다.
        while(!flag && !all_exit) {
            try {
                //작업
                this.sleep(100);
            } catch(InterruptedException e) {  }
        }
        System.out.println(this.getName() +"종료");
    }
    public void setFlag(boolean flag){
        this.flag = flag;
    }
} //end of ControlThread class

public class ControlThreadMain {
    public static void main(String args[])throws Exception{
        System.out.println("작업시작");
        ControlThread a = new ControlThread();
        ControlThread b = new ControlThread();
        ControlThread c = new ControlThread();
        a.start();
        b.start();
        c.start();
        Thread.sleep(100);
        int i;
        System.out.print("종료할 스레드를 입력하시오! A, B, C, M?\n");
        while(true){
            i = System.in.read();
            if(i == 'A'){
                a.setFlag(true);
            }else if(i == 'B'){
                b.setFlag(true);
            }else if(i == 'C'){
                c.setFlag(true);
            }else if(i == 'M'){
                //모든 스레드를 종료시킨다.
                ControlThread.all_exit = true;
                System.out.println("main종료");
                break;
            }
        }
    } //end of main
} //end of ControlThreadMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap08>javac ControlThreadMain.java
C:\javasrc\chap08>java ControlThreadMain
작업시작
종료할 스레드를 입력하시오! A, B, C, M?
Thread-1시작
Thread-2시작
Thread-3시작
A
Thread-1종료
B
Thread-2종료
C
Thread-3종료
M
main종료

C:\javasrc\chap08>java ControlThreadMain
작업시작
종료할 스레드를 입력하시오! A, B, C, M?
Thread-1시작
Thread-2시작
Thread-3시작
M
main종료
Thread-1종료
Thread-2종료
Thread-3종료
***/
───────────────────────────────────────ⓑ

이 예제에서는 조건을 두 개 두고 있습니다. flag는 스레드 각각을 제어하기 위한 것이며, all_exit는 모든 스레드를 제어하기 위한 것입니다. 

◈ public boolean all_exit = false;
◈ private boolean flag = false;

스태틱은 모든 객체에서 단 하나의 메모리만 생성되는 특징이 있기 때문에 all_exit의 설정을 변경하면 생성된 모든 스레드가 반응하게 됩니다. 위의 예에서 'M'이라는 문자를 입력받았을 때 모든 스레드는 다음의 구문에 반응하게 됩니다.

◈ ControlThread.all_exit = true;

while문의 조건에서 생성된 모든 스레드가 스태틱 멤버 변수 all_exit에 반응하기 때문에 각각의 스레드를 따로 제어하는 것이 아니라 한꺼번에 모든 스레드를 제어할 수 있는 것입니다.



8.3.4 스레드의 Resume, Suspend



스레드를 약간이라도 다루어 보신 분이라면 스레드의 작업을 잠깐 멈추게 하거나 멈춘 작업을 재개하는 기능을 생각할 것입니다. 구버전의 자바에서는 이러한 기능을 위해서 suspend()와 resume()이라는 메서드를 지원하고 있었습니다. 하지만 자바의 새로운 버전에서는 suspend()와 resume()은 다음과 같이 Deprecated 되었습니다.

▣ public final void suspend() Deprecated
◈ 스레드의 작업을 대기시킨다.

▣ public final void resume() Deprecated
◈ 스레드의 대기 상태를 해제하고 작업을 재개한다.

Deprecated에 대해서는 앞에서도 잠깐 언급했습니다. 더 이상 사용하지 않는 메서드를 의미합니다. 그렇다면 스레드를 수동으로 멈추게 하거나 작업을 재개하는 메카니즘이 자바의 스레드에는 없는 것일까요? 자바에서는 suspend()와 resume()을 사용하지 않고, wait()와 notify()를 이용해서 스레드의 대기 상태를 제어하게 됩니다. 이것에 대해서는 10장에서 다시 학습하게 될 것입니다.
저작자 표시 비영리 변경 금지

'JAVA PROGRAMMING > JAVA' 카테고리의 다른 글

9.1 Java Stream  (0) 2010/02/21
8.4 동기화  (0) 2010/02/21
8.3 스레드의 제어  (0) 2010/02/21
8.2 스레드의 기본  (0) 2010/02/21
8.1 Thread and Synchronization  (0) 2010/02/21
7.3 에러처리 실례  (0) 2010/02/21
1  ... 46 47 48 49 50 51 52 53 54  ... 159 
BLOG main image
(주)KinTeL 회장 김형기

카테고리

분류 전체보기 (159)
My Name Is KinTeL (3)
농구 인생 (1)
사진 이야기 (13)
미디어 (5)
JAVA PROGRAMMING (105)
C# Programming (2)
FLEX (6)
Database (4)
About Eclipse (1)
참조 - Reference (9)
모든 무료 정보 (2)
좋은글 (2)


Statistics Graph
Total : 73,500
Today : 11
Yesterday : 43

최근에 받은 트랙백