8.2 스레드의 기본

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


8.2.1 스레드란



하나의 프로세스 내에서 멀티 태스킹과 유사하게 여러 개의 메서드를 동시에 실행시킬 수 있을까요? 일반적인 프로그램의 경우에는 main()을 실행시키면 순서대로 작업이 진행됩니다. 한마디로 한다면 '시퀀셜(Sequential)하다'라는 의미입니다. 단 한순간에 하나의 메서드만이 동작하는 것입니다.

▣ 시퀀셜(Sequential)하게 동작한다.
◈ 한 순간에 하나의 메서드가 동작하는 것을 '시퀀셜하게 동작한다'라고 한다.

우리의 목표는 두 개의 메서드를 동시에 실행하는 것입니다. 이것이 바로 스레드(Thread)의 기본 목표이기도 합니다. 한 순간에 두 개의 메서드가 동시에 실행된다면 실행된 두 개의 메서드는 스레드라고 부를 수 있습니다. 두 개의 작업을 동시에 실행한다는 의미는 다음과 같이 정의를 내릴 수 있습니다.

▣ 스레드의 기본
◈ 한순간에 두 개의 메서드가 동시에 실행되었을 때, 실행된 메서드들을 스레드(Thread)라고 한다.

일반적인 프로그램들의 작업은 꼬리에 꼬리를 무는 형식으로 진행됩니다. main()도 하나의 메서드이기 때문에 하나의 main()이 실행되고 내부에 또 다른 메서드가 실행되더라도 대부분의 경우 동시에 작업이 진행되지는 않습니다. 그렇다면 다음과 같이 생각해 볼 수 있습니다.

▣ 생각
◈ 어떻게 동시에 두 개의 메서드를 실행시키는가?

두 개의 메서드를 동시에 실행하는 방법은 그렇게 만만한 작업이 아닙니다. C 언어로 이것을 구현한다면 jump를 이용해야 할 것입니다. 하나의 메서드에서 잠깐 작업을 하다가 다른 메서드로 jump해서 다시 잠깐 작업을 하면 가능할 수도 있습니다. 하지만 jump를 제어하는 것 또한 쉬운 일은 아닙니다. 그렇다고 너무 걱정할 필요는 없습니다. 대부분의 프로그램 언어에서 동시에 두 개의 메서드를 실행하기 위한 스레드 라이브러리를 제공하고 있습니다. 이것은 자바 또한 마찬가지입니다. 이쯤에서 우리는 스레드의 정의를 내리고 가도록 하죠.

▣ 스레드란?
◈ 프로그램에서 독립적으로 실행되는 메서드

스레드란 하나의 프로그램에서 독립적으로 실행되는 메서드입니다. 스레드가 메서드인 것은 사실이지만 조건이 있습니다. 하나의 메서드가 실행되는 것도 하나의 스레드가 실행되는 것이지만, 진정한 스레드는 동시에 두 개 이상의 메서드가 실행되는 것을 의미합니다. 두 개의 메서드가 존재하고 서로 독립된 메서드로써 동시에 작업을 진행할 수 있으면, 이 때 이 메서드들을 스레드(Thread)라고 부를 수 있습니다. 스레드를 배우면서 다음의 사항을 생각하면서 학습하시기 바랍니다.

▣ 스레드의 학습목표
◈ 스레드가 어떻게 만들어져서 실행되는가?
◈ 왜 스레드를 메서드라고 하는가?

스레드 프로그램을 해나가면서 여러분들이 스레드를 메서드라고 말할 수 있다면 스레드의 개념을 정확하게 잡은 것입니다. 자! 그렇다면 다른 것은 전부 제쳐두고 메서드를 두 개 만든 후 동시에 실행시키는 방법부터 알아보도록 하죠. 그리고 나서 스레드의 상태(Status), 우선권(Priority) 그리고 동기화(Synchronization)에 대해서 학습하도록 하겠습니다.



8.2.2 Runnable로 스레드 만들기



'스레드는 메서드다'라는 말을 꼭 기억하시기 바랍니다. 스레드의 장이 끝나기 전에 이것을 잊으시면 안됩니다. 이것만 제대로 이해하신다면 스레드와 관련된 동기화니 우선권이니 하는 것들은 저절로 이해하실 수 있습니다. 일단 여러분들은 스레드로 사용할 메서드를 만들어야 합니다. 스레드가 메서드라고 했으니 메서드를 하나 만들어야겠죠. 이 때 메서드의 이름은 정해져 있습니다. 바로 public void run()이라는 이름으로 작성해야 합니다. 그럼 여러분들은 다음과 같이 클래스 내에 run() 메서드를 만드려고 할 것입니다.

◈ public class Top{
◈         pubic void run(){
◈             //스레드의 세부 내용
◈         }
◈ }

임의의 클래스를 하나 만들고 그 안에 run()을 작성하려 할 것입니다. 이러한 방식보다도 더 효율적인 방법을 이용하기 위해서 사용하는 것이 바로 인터페이스 방식입니다. 자바에서는 run()의 사용을 편리하게 하기 위해서 다음과 같은 run() 메서드 단 하나만을 포함한 인터페이스를 지원해 주고 있습니다. 

▣ 스레드의 메서드를 구현하기 위한 인터페이스
◈ public interface Runnable{
◈         void run();
◈ }

여러분들은 단지 Runnable을 구현해서 run() 메서드만 재정의하면 되는 것입니다. 다음과 같이 Runnable을 구현하게 될 것입니다.

▣ Runnable 인터페이스의 구현
◈ public class Top implements Runnable{
◈         pubic void run(){
◈                 //...작업 내용
◈         }
◈ }

run()을 어차피 만들어야 하기 때문에 미리 인터페이스로 만들어 둔 것입니다. Runnable은 하나의 메서드밖에 없는 인터페이스입니다. 스레드는 하나의 메서드이기 때문에 메서드 하나만 하면 충분합니다. Runnable을 구현하고 run()까지 재정의했다면 이제 이것을 스레드화해야 합니다. 

현재까지는 단순히 run()이라는 메서드에 불과합니다. 물론 인터페이스를 구현하긴 했지만 인터페이스가 뼈대만 있다는 사실은 이미 5장에서 학습한 바 있습니다. 자! 그렇다면 어떻게 스레드를 만들까요? 이것의 방법은 수학공식과 같이 정해져 있습니다. 먼저 다음과 같이 Runnable을 구현한 Top 객체를 만들어 두어야 합니다. 그리고 진짜 스레드에 끼워 넣어서(장착시켜서) 스레드를 동작시키면 됩니다.

▣ Runnable 인터페이스를 구현하고 있는 Top형의 객체 생성
◈ Top t = new Top();

진짜 스레드는 Thread 클래스로 만들어야 합니다. Thread를 만들 때 Runnable을 구현한 Top형의 객체를 넣어주고(장착하고) 만들면, 내부에서 Runnable의 run() 메서드를 이용해서 스레드를 만들게 됩니다. 다음과 같이 Thread형의 객체를 만들 때 생성자의 매개변수로 Runnable을 구현한 객체를 넣어주기만 하면 됩니다.

▣ 진짜 스레드에 Runnable 장착
◈ Topt t = new Top();
◈ Thread thd = new Thread(t);

즉 진짜 스레드 Thread thd를 만들 때 생성자의 매개변수를 이용해서 Runnable을 장착하는 방식입니다. 물론 스레드의 작업에 해당하는 부분은 당연히 run() 부분입니다. 이렇게 만들어만 둔다고 스레드가 동작하는 것은 아닙니다. 이제 run()을 동작시켜야겠죠. Thread의 start()를 호출하면 내부에서 run()이 독립적으로 호출됩니다.

▣ 스레드 동작시키기
◈ Topt t = new Top(); //가짜 스레드
◈ Thread thd = new Thread(t); //진짜 스레드
◈ thd.start(); //스레드 동작시키기

결국 run()을 구현한 Runnable을 진짜 스레드에 장착하고, 그리고 Thread의 start()를 호출했을 때 내부에서 run()을 하나의 독립된 스레드로 동작시켜 주는 방식입니다. 몇 줄 되지 않는 구문이 아주 많은 의미를 담고 있습니다. run() 하나를 스레드로 동작시키기 위해서 Runnable과 Thread 그리고 Thread의 start()까지 필요한 것입니다. 이것을 프로그램으로 작성하면 다음과 같습니다.

☎ 『chap08\ThreadMain.java』
ⓙ───────────────────────────────────────
/**
Runnable을 구현해서 만드는 스레드(스레드를 생성하는 방법)
**/
class Top implements Runnable{
    public void run(){
        for(int i=0; i<50; i++)
            System.out.print(i+"\t");
    }
} //end of Top class

public class ThreadMain{
    public static void main(String[] args){
        System.out.println("프로그램 시작");
        Top t = new Top();
        Thread thd = new Thread(t);
        thd.start();
        System.out.println("프로그램 종료");
    } //end of main
} //end of ThreadMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap08>javac ThreadMain.java
C:\javasrc\chap08>java ThreadMain
프로그램 시작
프로그램 종료
0       1       2       3       4       5       6       7       8       9
10      11      12      13      14      15      16      17      18      19
20      21      22      23      24      25      26      27      28      29
30      31      32      33      34      35      36      37      38      39
40      41      42      43      44      45      46      47      48      49
***/
───────────────────────────────────────ⓑ

프로그램의 결과를 보시면 이상한 것을 발견할 수 있습니다. '프로그램 시작'과 '프로그램 종료'가 출력되고 난 뒤에 스레드의 작업이 진행되는 것입니다. main()이 작업을 끝낸 후 동작중인 스레드가 작업을 끝낼 때까지 대기하기 때문입니다. 이러한 원리는 다음과 같이 표현할 수 있습니다.

스레드의 생성과 실행【chap08\runnablestart.bmp】
 




main()도 하나의 스레드로써 동작하다가 다른 스레드를 동작시키고 있는 것입니다. 그리고 start()하는 순간 동시에 작업이 이루지고 있는 것입니다. 그렇기 때문에 '프로그램 종료' 메시지가 먼저 출력이 된 것입니다.

결과적으로 보면 run()이라는 메서드를 동시에 하나 더 실행시키는 기법입니다. 스레드를 하나 더 띄워 보도록 하죠. 다음과 같이 한다면 똑같은 메서드에 해당하는 두 개의 스레드가 동작하는 것입니다. 즉 독립적으로 메서드를 두 개 만들어서 띄우는 것이 됩니다.

『chap08\ThreadMain2.java』
ⓙ───────────────────────────────────────
/**
두 개의 스레드를 생성한 후 실행하는 예(Runnable 구현)
**/
class Top implements Runnable{
    public void run(){
        for(int i=0; i<50; i++)
            System.out.print(i+"\t");
    }
} //end of Top class

public class ThreadMain2{
    public static void main(String[] args){
        System.out.println("프로그램 시작");
        //1. Runnable을 구현하는 객체 만들기
        Top t = new Top();
        //2. Runnable을 장착한 후 진짜 스레드 만들기
        Thread thd1 = new Thread(t);
        Thread thd2 = new Thread(t);
        //3. 스레드 동작 시키기
        thd1.start();
        thd2.start();
        System.out.println("프로그램 종료");
    } //end of main
} //end of ThreadMain2 class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap08>javac ThreadMain2.java
C:\javasrc\chap08>java ThreadMain2
프로그램 시작
프로그램 종료
0       1       2       3       4       5       6       7       8       9
10      11      12      13      14      15      0       1       2       3
4       5       6       7       8       9       10      11      12      13
14      15      16      17      18      19      20      21      22      23
24      25      26      27      28      29      30      31      32      16
17      18      19      20      21      22      23      24      25      26
27      28      29      30      31      32      33      34      35      36
37      38      39      40      41      42      43      44      45      46
47      48      49      33      34      35      36      37      38      39
40      41      42      43      44      45      46      47      48      49
***/
───────────────────────────────────────ⓑ

위의 결과를 보시면 번갈아가면서 숫자가 출력되는 것을 확인할 수 있을 것입니다. main()과 Runnable 2개를 합쳐서 전체 3개의 스레드가 동작하고 있는 것입니다. 위의 예제는 다음과 같은 방식으로 동작합니다.

다중 스레드의 생성과 실행【chap08\runnable-two-start.bmp】
 



위의 예제에서 다음과 같이 하나의 t를 이용해서 2개의 스레드를 만들고 있습니다. 

◈ Top t = new Top();
◈ Thread thd1 = new Thread(t);
◈ Thread thd2 = new Thread(t);

여러분이 메서드를 두 번 호출할 수 있듯이 똑같은 run()을 이용해서 두 개의 스레드를 만들 수 있습니다. 

▣ 참고
◈ C나 C++에서는 스레드를 구현하기 위해서 함수 포인터(Function Pointer)라는 것을 이용합니다. 하지만 자바에서는 함수 포인터의 개념이 없기 때문에 Runnable 인터페이스를 이용하는 것입니다. 혹시 함수 포인터의 개념을 알고 계시다면 Runnable 인터페이스가 하는 역할과 비교해 보시기 바랍니다.



8.2.3 Thread 상속으로 스레드 만들기



Runnable을 이용해서 새로운 메서드를 작성하고 그리고 스레드를 생성하고 실행하는 예를 알아보았습니다. Runnable은 하나의 메서드를 만드는 역할을 하며, 메서드의 이름은 무조건 run()이 되어야 합니다. 이러한 방법 이외에 Thread를 직접 상속한 후 Thread 내부에서 직접 run()을 재정의해서 구현하는 방법도 있습니다. 다음과 같이 Runnable을 이용하지 않고 Thread를 바로 상속한 후 run()을 작성할 수도 있습니다.

▣ Thread를 상속받는 방법(상속을 통해서 스레드를 만드는 방법)
◈ public class DerivedThread extends Thread{
◈         public void run(){
◈             //...스레드 내의 작업
◈         }
◈ }

위와 같이 스레드를 구현하였다면 다음과 같이 객체를 생성한 후 바로 start() 메서드를 호출할 수 있습니다.

▣ 스레드 생성 및 동작시키기
◈ DerivedThread d = new DerivedThread();
◈ d.start();

Runnable보다는 간단하지만 run()을 사용한다는 측면에서는 동일합니다. 이것을 테스트하는 예제는 다음과 같습니다.

『chap08\DerivedThreadMain.java』
ⓙ───────────────────────────────────────
/**
Thread를 상속하는 스레드 만들기(Thread 상속)
**/
class DerivedThread extends Thread{
    public void run(){
        for(int i=0; i<50; i++)
            System.out.print(i+"\t");
    }
} //end of DerivedThread class

public class DerivedThreadMain{
    public static void main(String[] args){
        System.out.println("프로그램 시작");
        DerivedThread d = new DerivedThread();
        d.start();
        System.out.println("프로그램 종료");
    } //end of main
} //end of DerivedThreadMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap08>javac DerivedThreadMain.java
C:\javasrc\chap08>java DerivedThreadMain
프로그램 시작
프로그램 종료
0       1       2       3       4       5       6       7       8       9
10      11      12      13      14      15      16      17      18      19
20      21      22      23      24      25      26      27      28      29
30      31      32      33      34      35      36      37      38      39
40      41      42      43      44      45      46      47      48      49
***/
───────────────────────────────────────ⓑ

위의 출력결과는 Runnable로 스레드를 구현했을 때와 동일합니다. 두 개의 스레드를 동작시키는 예도 만들어 보도록 하죠.

『chap08\DerivedThreadMain2.java』
ⓙ───────────────────────────────────────
/**
Thread를 상속하는 두 개의 스레드를 생성한 후 실행시키기(Thread 상속)
**/
class DerivedThread extends Thread{
    public void run(){
        for(int i=0; i<50; i++)
            System.out.print(i+"\t");
    }
} //end of DerivedThread class

public class DerivedThreadMain2{
    public static void main(String[] args){
        System.out.println("프로그램 시작");
        DerivedThread d1 = new DerivedThread();
        DerivedThread d2 = new DerivedThread();
        d1.start();
        d2.start();
        System.out.println("프로그램 종료");
    } //end of main
} //end of DerivedThreadMain2 class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap08>javac DerivedThreadMain2.java
C:\javasrc\chap08>java DerivedThreadMain2
프로그램 시작
프로그램 종료
0       1       2       3       4       5       6       7       8       9
10      11      12      13      14      15      0       1       2       3
4       5       6       7       8       9       10      11      12      13
14      15      16      17      18      19      20      21      22      23
24      25      26      27      28      29      30      31      32      16
17      18      19      20      21      22      23      24      25      26
27      28      29      30      31      32      33      34      35      36
37      38      39      40      41      42      43      44      45      46
47      48      49      33      34      35      36      37      38      39
40      41      42      43      44      45      46      47      48      49
***/
───────────────────────────────────────ⓑ

Thread의 상속으로 구현하든, Runnable을 이용해서 구현하든, 스레드로써 동작하는 것은 같습니다. 하지만 Runnable은 인터페이스이며 Thread는 클래스라는 점을 감안해서 사용해야 합니다. 만약 여러분이 다른 클래스를 미리 상속한 상태라면 Runnable을 이용하는 것이 바람직할 것입니다. 그렇지 않다면 Thread를 상속하는 것이 훨씬 편합니다. 상속 자체로 Thread의 모든 기능을 사용할 수 있기 때문에 훨씬 편리합니다.




8.2.4 Runnable을 사용하는 이유



Thread 클래스를 상속받는 것과 Runnable 인터페이스를 구현하는 것은 별다른 차이가 없습니다. 알고 보면 Thread를 상속하는 것이 휠씬 편한 방법입니다. 상속의 장점을 최대한 살릴 수 있는 것이죠. 부모 클래스인 Thread 클래스의 메서드를 마음대로 이용할 수 있다는 것은 객체지향 자체의 장점이니까요! 하지만 굳이 Runnable 인터페이스를 사용하는 이유는 여러분의 클래스가 다른 클래스를 미리 상속했기 때문입니다. 자바는 단일 상속을 기본으로 하기 때문에 다른 클래스를 상속했다면 Thread를 상속할 수 없습니다. 다음의 경우를 한번 보시죠.

▣ extends Frame을 상속했기 때문에 Thread를 상속할 수 없는 경우
◈ class RunFrame extends Frame{
◈         //...작업
◈ }

자바의 기본 법칙상 다중 상속은 금지하고 있습니다. 이러한 이유 때문에 Runnable 인터페이스를 구현하는 방식으로 단일 상속의 한계를 피해 나가는 것입니다. 다음과 같이 수정한다면 Frame도 상속할 수 있으며, 스레드를 위한 Runnable도 구현할 수 있습니다.

▣ Frame은 상속을 하고 Runnable은 구현을 한 상태
◈ class RunFrame extends Frame implements Runnable{
◈         public void run(){
◈             //...스레드 내의 작업
◈         }
◈ }

▣ Runnable을 사용하는 이유
◈ Thread를 상속하기 이전에 다른 클래스를 상속한 경우 Runnable을 이용해서 스레드를 만든다.

위와 같은 방법으로 클래스를 만들었다면 다음과 같이 창을 띄우난 뒤 스레드를 동작시켜야 합니다. 

▣ 창 띄우기와 스레드 동작 시키기
◈ RunFrame r = new RunFrame();
◈ r.setSize(300, 100);
◈ r.show();
◈ Thread t = new Thread(r);
◈ t.start();

다음은 Frame의 상속으로 인해 Thread를 상속할 수 없기 때문에 Runnable 인터페이스를 구현하는 예를 보여주고 있습니다.

『chap08\RunFrameMain.java』
ⓙ───────────────────────────────────────
/**
Frame의 상속과 Runnable의 구현
Thread를 상속하지 못하는 경우 Runnable로 구현
**/
import java.awt.*;
class RunFrame extends Frame implements Runnable {
    public void run() {
        int i = 0;
        System.out.println("스레드 시작!");
        while(i<20) {
            System.out.print(i + "\t");
            this.setTitle("스레드 동작중" + i++);
            try{
                Thread.sleep(300);
            }catch(InterruptedException e){System.out.println(e);}
        }
        System.out.println("스레드 종료!");
    }
} //end of RunFrame class

public class RunFrameMain{
   public static void main(String args[]){
       RunFrame r = new RunFrame();
       r.setSize(300, 100);
       r.show();
       Thread t = new Thread(r);
       t.start();
   } //end of main
} //end of RunFrameMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap08>javac RunFrameMain.java
C:\javasrc\chap08>java RunFrameMain
【chap08\runnableFrame.bmp】


 


스레드 시작!
0       1       2       3       4       5       6       7       8       9
10      11      12      13      14      15      16      17      18      19
스레드 종료!
***/
───────────────────────────────────────ⓑ

이 예제에서 Frame을 먼저 상속하고 있기 때문에 Runnable을 이용해서 스레드를 구현하고 있습니다. 

▣ Frame은 상속하고 Runnable은 구현한 경우
◈ class RunFrame extends Frame implements Runnable{
◈         public void run(){
◈             //...스레드 내의 작업
◈         }
◈ }

▣ 제어권
◈ 만약 위의 예제를 스레드로 구현하지 않고 일반 메서드를 호출하는 방식으로 구현한다면 while문이 끝날 때까지 Frame의 다른 작업을 할 수가 없습니다. 스레드로 구현하지 않으면 while문이 끝나야만 다른 작업을 할 수 있습니다.

자바는 단일 상속을 기본으로 하기 때문에 Frame이 먼저 상속된 상태라면 Thread 대신 Runnable을 이용하면 됩니다.

RunFrame 클래스는 2개의 상위 클래스를 보유하고 있습니다. Frame과 Runnable이 그것이죠. 5장의 다형성 부분에서 인터페이스가 클래스의 한 종류라는 것을 학습했습니다. 인터페이스도 상위 클래스로서의 역할을 할 수 있으며 업캐스팅 또한 가능합니다. 현재 Frame과 Runnable은 동등한 부모 클래스의 자격이 부여되어 있는 것입니다. 그렇기 때문에 다음과 같은 구문이 가능합니다.

▣ RunFrame으로 가능한 업캐스팅
◈ Frame f = new RunFrame(); //Frame으로의 캐스팅
◈ Runnable r = new RunFrame(); //Runnable로의 캐스팅

RunFrame 클래스는 Frame으로 업캐스팅이 가능하며, Runnable로도 업캐스팅이 가능합니다. 위의 RunFrame의 경우 다음과 같이 2가지 작업을 해주어야 합니다.

◈ 창만들고 띄우기
◈ 스레드 동작시키기

먼저 다음과 같이 RunFrame을 생성한 후 화면에 디스플레이합니다.

▣ 창 띄우기
◈ RunFrame r = new RunFrame();
◈ r.setSize(300, 100);
◈ r.show();

그리고 앞에서 배운 Runnable의 실행 방법을 이용해서 다음과 같이 스레드를 start() 시키면 됩니다.

▣ 스레드 동작시키기
◈ Thread t = new Thread(r);
◈ t.start();

RunFrame r을 Thread의 매개변수로 줄 수 있는 이유는 업캐스팅 때문입니다. Runnable을 필요로 하는 Thread의 생성자는 다음과 같습니다.

▣ Thread 클래스의 생성자
◈ public Thread(Runnable target)

여기서 Runnable을 매개변수로 주어야 하지만 RunFrame을 매개변수로 주어도 상관없습니다. 그것은 매개변수로 삽입될 때 다음과 같은 업캐스팅 현상이 일어나기 때문입니다.

▣ Thread 객체를 생성할 때 매개변수의 업캐스팅
◈ Runnable target = r; //r은 RunFrame의 객체

이와 비슷한 구문을 자주 만나게 될 것입니다. Vector의 데이터 삽입에서도 이와 비슷한 구문을 사용했으며, Runnable을 사용할 때에도 이러한 업캐스팅이 사용되는 것입니다. 그리고 스트림, 직렬화, 이벤트 처리 등 계속해서 이러한 업캐스팅의 구문을 만나게 될 것입니다. 업캐스팅의 현상은 아무리 강조해도 지나치지 않습니다. 반드시 숙지하시기 바랍니다.

위의 예에서 창을 만들고 스레드를 동작시키는 방법은 프로그램적으로 그렇게 세련되지 못한 기법입니다. 프로그램은 동작합니다만 그렇게 매끄러운 구문은 아닙니다. 여러분들의 이해를 돕기 위해서 main()에서 스레드를 동작시켰지만, 실제로는 Frame 내부에서 스레드를 생성시키고 자동으로 스레드가 동작하는 방식을 더 많이 사용합니다.  다음은 Frame 내부에서 스레드를 동작시키는 방법을 보여주고 있습니다.

▣ 클래스 내부에서 스레드 구동 시키기
◈ class RunnableFrame extends Frame implements Runnable {
◈     public RunnableFrame() {
◈         new Thread(this).start(); //Runnable을 이용한 스레드의 구동
◈     }
◈     //생략...    
◈ }

다음은 일반적인 스레드 생성 및 실행 방법을 테스트하는 예입니다.

『chap08\RunnableFrameMain.java』
ⓙ───────────────────────────────────────
/**
Frame 내부에서 스레드 동작 시키기
**/
import java.awt.*;
class RunnableFrame extends Frame implements Runnable {
    public RunnableFrame() {
        new Thread(this).start();
    }
    public void run() {
        int i = 0;
        System.out.println("스레드 시작!");
        while(i<20) {
            System.out.print(i + "\t");
            this.setTitle("스레드 동작중" + i++);
            try{
                Thread.sleep(300);
            }catch(InterruptedException e){System.out.println(e);}
        }
        System.out.println("스레드 종료!");
    }
} //end of RunnableFrame class

public class RunnableFrameMain{
   public static void main(String args[]){
       RunnableFrame r = new RunnableFrame();
       r.setSize(300, 100);
       r.show();
   } //end of main
} //end of RunnableFrameMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap08>javac RunnableFrameMain.java
C:\javasrc\chap08>java RunnableFrameMain
【chap08\runnableFrame.bmp】


 


스레드 시작!
0       1       2       3       4       5       6       7       8       9
10      11      12      13      14      15      16      17      18      19
스레드 종료!
***/
───────────────────────────────────────ⓑ

앞의 RunFrame과 RunnableFrame을 잘 비교해 보시기 바랍니다. RunFrame의 경우 main()에서 스레드를 생성한 후 실행했지만, RunnableFrame은 RunnableFrame 객체를 생성할 때 생성자에서 스레드를 만든 후 실행하고 있습니다. 다음은 생성자에서 스레드를 생성하고 실행하는 구문입니다.

▣ RunnableFrame 클래스 내에서 스레드 동작 시키기
◈ new Thread(this).start(); //this는 RunnableFrame형이다.

이와 같은 구문이 가능한 이유 또한 업캐스팅 때문입니다. this는 언젠가 생성될 메모리를 의미하며 어차피 this 자체는 Runnable로 캐스팅될 수 있기 때문에 위와 같은 구문이 가능한 것입니다. 프로그램적인 기법이기 때문에 어느 것을 사용하셔도 상관없지만 RunFrame보다 RunnableFrame 방식이 더 일반적인 기법입니다.

만약 위의 프로그램에서 Runnable을 사용하지 않고 Thread로 구현하겠다면, Thread를 상속할 수 없기 때문에 Frame과 Thread를 분리시켜야 합니다. 프로그램이 복잡하거나 독립적으로 스레드를 관리하고 싶다면, 다음과 같이 스레드를 독립적으로 구현해서 사용할 수도 있습니다.

『chap08\SoloFrameMain.java』
ⓙ───────────────────────────────────────
/**
Frame과 Thread 분리시켜서 구현
**/
import java.awt.*;
class SoloFrame extends Frame{
    public SoloFrame(){
        SoloThread t = new SoloThread(this);
        t.start();
    }
} //end of SoloFrame class

class SoloThread extends Thread{
    private Frame f = null;
    public SoloThread(Frame f){
        this.f = f;
    }
    public void run() {
        int i = 0;
        System.out.println("스레드 시작!");
        while(i<20) {
            System.out.print(i + "\t");
            f.setTitle("스레드 동작중" + i++);
            try{
                this.sleep(300);
            }catch(InterruptedException e){System.out.println(e);}
        }
        System.out.println("스레드 종료!");
    }
} //end of SoloThread class

public class SoloFrameMain{
   public static void main(String args[]){
       SoloFrame s = new SoloFrame();
       s.setSize(300, 100);
       s.show();
   } //end of main
} //end of SoloFrameMain class
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap08>javac SoloFrameMain.java
C:\javasrc\chap08>java SoloFrameMain
【chap08\runnableFrame.bmp】
 


스레드 시작!
0       1       2       3       4       5       6       7       8       9
10      11      12      13      14      15      16      17      18      19
스레드 종료!
***/
───────────────────────────────────────ⓑ

위의 예제에서는 다음과 같이 프레임과 스레드를 분리시켜서 구현하고 있습니다. 

▣ 프레임과 스레드를 분리시켜서 구현
◈ class SoloFrame extends Frame{...}
◈ class SoloThread extends Thread{...}

그런데 SoloThread에서 타이틀바에서 제목을 출력하기 위해서는 반드시 SoloFrame의 참조값이 필요합니다. SoloThread에서 참조값을 넘겨받기 위해서 다음과 같이 생성자에서 SoloFrame의 참조값을 받은 뒤 멤버 변수에 저정해 두고 있습니다.

▣ 스레드에 프레임의 참조값 셋팅
◈ private Frame f = null;
◈ public SoloThread(Frame f){
◈        this.f = f; //참조값복사
◈}

run() 메서드에서는 이 참조값 f를 이용해서 setTitle()을 호출하고 있습니다. 생성자에 매개변수로 들어온 참조값은 참조값복사에 의해서 참조값만 넘겨받게 됩니다. 그리고 SoloThread 내부에서 이를 이용하는 것입니다. 또 한가지 짚고 넘어가야 하는 것이 바로 this.sleep(300)입니다. sleep()이라는 메서드는 스레드를 일정시간 동안 대기시키는 역할을 하게 됩니다. sleep(300)은 1/1000초 단위로 사용하며, 0.3초동안 현재의 스레드를 대기상태로 만들기 때문에 다른 스레드들이 활동할 시간이 많아지게 되는 것입니다.

▣ sleep() 메서드
◈ sleep() 메서드는 현재의 스레드를 일정시간 동안 대기상태로 만든다.

실제 스레드를 생성하는 것은 SoloFrame의 생성자에서 하고 있습니다. 다음과 같이 스레드를 생성할 때 SoloFrame의 참조값만 넘겨준 후 일반 스레드처럼 start()만 호출해 주면 됩니다. 

▣ SoloThread 시작
◈ SoloThread t = new SoloThread(this); //this는 SoloFrame형
◈ t.start();

위의 3가지 방법중 첫 번째 방법을 제외한 두 번째와 세 번째는 많이 사용하는 기법입니다. 간단하게 스레드를 구현하고자 한다면 Runnable을 구현하면 될 것입니다. 그리고 독립적으로 스레드를 관리하고자 한다면, Thread를 상속받는 클래스를 따로 만들어 사용하면 됩니다. 실제 프로그램을 작성할 때 스레드 내의 프로그램이 상당히 길어질 수도 있기 때문에 스레드를 독립적으로 관리하는 경우도 많이 사용합니다. 세 가지를 비교해 가면서 스레드의 사용법을 익히시기 바랍니다.



8.2.5 스레드의 상태



Runnable을 구현해서 만들든 Thread를 상속해서 만들든 하나의 스레드로써 동작하는 것은 동일합니다. 스레드를 만들었다면 여러분들은 스레드를 제어하려 할 것입니다. 스레드를 만들고 제대로 실행하더라도 프로그래머가 스레드를 제어할 수 없다면 스레드는 쓸모 없는 존재가 되고 맙니다. 

▣ 스레드의 제어
◈ 스레드를 만들고 시작시키는 것보다 스레드를 제어하는 것이 더 중요하다.

사실 스레드를 만들고 실행시키는 것은 별로 어렵지 않습니다. 스레드를 어떻게 제어하느냐가 더 큰 문제입니다. 앞으로 스레드의 장을 학습하면서 계속해서 스레드의 제어에 대해서 배우게 될 것입니다. 스레드를 제대로 제어하려면 먼저 스레드의 상태부터 알아야 합니다. 이 절에서 스레드의 상태를 학습하도록 하겠습니다.

지금까지 테스트한 스레드는 일정한 작업을 한 후 바로 종료하는 방식이었지만 지속적으로 작업을 하는 경우가 더 많기 때문에 스레드의 제어는 필수적입니다. 스레드를 올바르게 제어하기 위해서 여러분들은 먼저 스레드의 상태부터 알고 있어야 합니다. 스레드의 상태는 크게 5가지로 나뉘어집니다.

▣ 스레드의 상태
◈ 시작 상태(Start 상태)
◈ 동작할 수 있는 상태(Runnable 상태)
◈ 동작 상태(Run 상태) - 한순간에 단 하나의 스레드만이 Run 상태가 된다.
◈ 대기 상태(NotRunnable 상태)
◈ 종료 상태(Dead 상태)

아주 간단합니다. 제일 먼저 스레드를 만든 후 start()를 호출했을 때 Start 상태가 됩니다. 그리고 바로 Runnable 상태로 진입하게 됩니다. Start 상태는 단순히 시작 단계에서 거치는 과정에 불과하기 때문에 그렇게 중요한 상태는 아닙니다. Start 상태와 Runnable 상태를 함께 묶어서 Runnable 상태로 보아도 됩니다.

▣ Runnable 상태
◈ 스레드가 실행되면 일반적으로 Runnable 상태가 된다.
◈ Runnable 상태의 스레드는 여러 개 존재할 수 있다.

Runnable 상태에서 CPU의 제어권을 할당받는 순간 Run 상태가 되었다가, 일정 시간동안 작업을 한 후 Runnable 상태로 되돌아와서 자신의 순서가 되기를 기다립니다. 아무리 작업이 빠르게 이루어지더라도 한순간에 단 하나의 작업만이 Run 상태에 있을 수 있습니다. 그렇기 때문에 Runnable 상태는 Run으로 들어가기 전단계의 중간단계라고 말할 수 있습니다. 

▣ Run 상태
◈ Runnable 상태에서만 Run 상태가 될 수 있다.
◈ 한순간 단 하나의 스레드만이 Run 상태가 된다.

Run 상태에서는 단 하나의 작업만 존재하지만 Runnable 상태에서는 여러 개의 스레드가 존재하면서 처리될 순서를 기다리게 됩니다. 사실 Runnable 상태를, 작업이 진행되고 있는 상태라고 보아도 상관없습니다. 오직 Runnable 상태에 있는 스레드만이 Run 상태가 될 수 있으며, 일정 시간동안 작업을 처리하고나면 다시 Runnable 상태가 됩니다. 즉 Runnable 상태에 있는 스레드끼리 작업을 번갈아 가면서 처리하는 방식입니다.

▣ Runnable과 Run의 상태 변화
◈ Runnable 상태에 있는 스레드들끼리 번갈아 가면서 Run 상태가 된다.
◈ Run 상태가 될 때 스레드는 작업을 진행할 수 있다.

Runnable 상태에서 몇 번의 Run 상태를 거친 후 모든 작업을 완료하면 Dead 상태가 됩니다. 메서드의 호출이 끝나듯이 스레드도 끝을 맞이하는 것입니다. 이렇게 되면 스레드는 자동으로 소멸하게 됩니다. run()의 종료는 곧 스레드의 종료를 의미합니다. 

▣ Dead 상태(스레드의 종료)
◈ run() 메서드의 종료는 스레드의 종료를 의미한다.

그런데 NotRunnable 상태는 약간 다릅니다. NotRunnable 상태는 Runnable 상태를 벗어나 잠깐 쉬고 있는 상태라고 말할 수 있습니다. 즉 Run 상태로는 진입할 수는 없지만 Dead 상태는 아닌 쉽게 대기 상태라고 하는 것이 좋겠군요. 

▣ NotRunnable 상태
◈ NotRunnable 상태는 Run 상태로 진입할 수는 없지만 Dead 상태는 아닌 대기 상태 

축구팀에도 1군이 있고 2군이 있지 않습니까! 2군의 경우 당장은 경기를 할 수 없어도 언젠가는 경기를 할 수 있는 대기 상태에 있는 것과 비슷합니다. 이 대기 상태를 바로 NotRunnable 상태라고 합니다. 이 경우에는 반드시 Runnable 상태로 진입한 후에 Run 상태가 될 수 있습니다. 이러한 상태를 그림으로 표현해 보도록 하죠.

스레드의 상태【chap08\threadstatus.bmp】
 

 

위의 그림에서처럼 Start, Runnable, Run, NotRunnable, Dead 상태로 나뉘며, NotRunnable 상태를 제외하고는 대부분 자동으로 이루어집니다. NotRunnable 상태의 경우에는 프로그래머가 직접 제어해 주어야 합니다. 만약 5초정도만 NotRuunable 상태로 만들고 싶다면 다음과 같이 sleep()을 사용하면 됩니다.

▣ 5초동안 NotRunnable 상태 만들기
◈ Thread.sleep(5000);

sleep()은 Thread의 스태틱 메서드이기 때문에 프로그램 어디에서나 사용할 수 있으며, sleep(5000)이 추가된 스레드는 5초 동안 NotRunnable 상태에 있다가 5초가 지나면 자동으로 Runnable 상태로 되돌아와 작업을 재개하게 됩니다. 이와는 약간 다른 wait()를 사용했을 경우에는 notify()를 호출해 주기 전까지는 무한정 NotRunnable 상태에 있게 됩니다. 만약 프로그래머가 wait()를 한 상태에서 잊어버리고 그냥 놓아 둔다면 영원히 대기상태에 빠지게 됩니다. 여러분이 wait()와 notify()까지 자유자재로 다룰 수 있다면 스레드 프로그램은 더 이상 배울 것이 없을지도 모릅니다. 

▣ NotRunnable 상태를 만드는 방법
◈ sleep()은 일정 시간동안만 NotRunnable 상태로 만든다.
◈ wait()와 notify()는 수동으로 NotRunnable 상태로 들어가고 나오는 것을 제어할 수 있다.

사실 스레드에서 제일 까다로운 것이 바로 wait()와 notify()입니다. wait()와 notify()는 이 장에서 다루지 않고 10장에서 다루도록 하겠습니다. 반드시 이 장을 학습한 뒤 10장의 wait()와 notify()를 확인하시기 바랍니다. 스레드의 백미(白眉)는 바로 wait()와 notify()이기 때문에 놓치지 마시기 바랍니다.




저작자 표시 비영리 변경 금지

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

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
7.2 Exception  (0) 2010/02/21
1  ... 47 48 49 50 51 52 53 54 55  ... 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

최근에 받은 트랙백