본문 바로가기

공부방/Java

자바 쓰레드 프로그래밍

쓰레드 기초

"실행중인 프로그램"을 프로세스라고한다. 그러니까 명령어를 입력해서 돌아가는 프로그램은 전부 프로세스인것이다. 프로세스에는 명령어들이 순차적으로 실행이되는데 이것을 실행 쓰레드하고 한다. 순차적으로 실행되는 명령어들을 하나의 실로 꿸 수 있기 때문이다. 이렇게 하나의 실로 꿸 수 있는 프로그램은 단일 쓰레드 프로그램이라고 한다. 그런데 어떤 프로그램에는 여러 개의 함수들이 서로 독립적으로 수행되는 경우도 있다. 이런 경우에는 명령어들이 순차적으로 수행되는 것이 아니기 때문에 하나의 실로 꿸수 없을 것이다. 이런 프로그램은 다중 쓰레드 프로그램이라고 한다.

다음 그림은 세차하는 프로그램이다. 홍길동이 스케쥴표에 나와 있는대로 세차를 하고 있다. 여기서 스케쥴표는 프로그램 명령어들이고, 스케쥴표대로 일을 하는 홍길동은 쓰레드가 된다. 홍길동이 세차하기 위해 사용하는 비누, 물, 호수 등은 시스템 자원들이다. 

 


다음 그림은 홍길동이 두 동생(홍길숙, 홍길찬)을 데리고 세차를 하고 있다. 이 세명은 각자 스케쥴 에 맞게 자신에게 할당된 일을 한다. 스케쥴표는 프로그램 명령어들이고, 홍길동, 홍길숙, 홍길찬은 일을 하는 쓰레드이다. 이렇게 일하는 쓰레드가 많은 프로그램을 다중 쓰레드 프로그램이라고 한다. 우선 직관적으로 봐도 다중 쓰레드 프로그램이 단일 쓰레드 프로그램보다 효율적일 것이다. 다중 쓰레드를 사용하는 프로그램이 대부분 효과적이지만 항상 그런 것은 아니다. "사공이 많으면 배가 산으로 간다"라는 속담이 있는 것 처럼 쓰레드가 많으면 어떤 경우에는 비효율적인 경우도 있다. 


쓰레드는 Thread 클래스로 부터 상속을 받아서 만들수도 있고, Runnable 인터페이스를 인플리멘츠해서 사용할 수 도 있다. 두가지 방법중에서 프로그래머는 그때 그때 상황에 맞게 사용하면 된다. 두 방법 모두 쓰레드를 사용하려면 run() 함수를 만들어야 한다. 이 run()함수에 쓰레드가 일할 작업의 내용을 기술해 놓는 것이다.

쓰레드를 만든 다음에 쓰레드를 실행 시키려면 start()함수를 호출해주어야 한다. start() 함수를 호출하면 자동적으로 run()함수가 수행된다. 쓰레드는 run()함수를 마치거나 stop()함수가 호출되면 실행을 종료한다. 종료된 쓰레드는 start()함수로 다시 실행시킬 수는 없다.

쓰레드의 스케쥴링은 자바 가상 머신에서 이루어진다. 자바 가상 머신은 우선 순위에 따라 선점 방식(preemtive) 방식으로 스케쥴링된다. 선점방식이란 우선 숭위가 높은 쓰레드가 등장하면 현재 실행중인 쓰레드는 쫏겨나고 우선순위가 높은 쓰레드가 실행되는 것을 의미한다. 우선 순위가 낮은 쓰레드는 우선 순위가 높은 쓰레드가 종료하거나 sleep, blocked 될때 까지 기다려야 한다.
  

예제: CountThread
CountThread.java 파일
   class CountThread extends Thread {
           int maxcount;
     
           CountThread(int maxcount) {
               this.maxcount = maxcount;
           }
     
           public void run() {
     
              for(int count = 1;count 
쓰레드가 종료되면 ThreadDeath 예외가 발생한다. 어떤 경우에는 쓰레드가 종료하기 전에 꼭 해주어야 할
일들이 있을 수도 있다. 이런 경우에 쓰레드가 종료할 때 발생되는 ThreadDeath 예외를 잡아서 쓰레드 종료
직전에 필요한 작업들을 처리할 수 있다. 모든 작업이 처리된 다음에는 ThreadDeath 예외를 다시 throw
해주어야 한다.

CatchDeath.java 파일

 class MyThread extends Thread {
           MyThread(String name) {
               super(name);
           }
        
           public void run() {
               try {
                   while (true) {
                       sleep(500);
                      System.out.println(getName() + ": is running");
                  }
              } catch (ThreadDeath ouch) {
                  System.out.println("I ("+getName() + ") is being killed.");
                  throw(ouch);
              } catch (InterruptedException e) {}
          }
      }
    
      public class CatchDeath {
          static public void main(String args[]) {
              MyThread thr_a = new MyThread("A");
    
              System.out.println("Starting the thread...");
              thr_a.start();
    
              try {
                  Thread.sleep(2000);
              } catch (InterruptedException e) {}
    
              System.out.println("Stopping the thread...");
              thr_a.stop();
          }
      }

결과 % java CatchDeath Starting the thread... A: is running A: is running A: is running Stopping the thread... I (A) is being killed.

설명 3 super(name); 쓰레드에 이름을 붙이기 위해서 Thread(String name) 생성자를 사용할 수 있다. super(name)은 상위 클래스의 생성자, 즉 Thread(String name)를 의미한다. 12 } catch (ThreadDeath ouch) { 13 System.out.println("I ("+getName() + ") is being killed."); 14 throw(ouch); 쓰레드가 종료되면 ThreadDeath 예외가 발생한다. 13번째 줄에서 쓰레드가 종료하기 전에 처리할 작업들을 처리할 수 있다. 작업이 끝나면 ThreadDeath 예외를 다시 throw 해서 쓰레드가 종료되도록한다.

8.2 자바 쓰레드

다음은 쓰레드의 상태도이다. 쓰레드는 생성자를 통해 생성되면 Newborn 상태에 있게된다. Newborn 상태에서 start() 메소드가 호출되면 Runnable 상태로 전이된다. Runnable 상태는 쓰레드가 CPU의 디스패치(dispatch) 큐 (queue)에 등록된 상태이다. Runnable 상태에서 Running 상태로의 전이는 운영체제에 의해 자동적으로 이루어진다. Runnable 상태에 우선 순위에 따라 가장 높은 우선 순위의 쓰레드가 Running 상태로 전이된다. Running 상태는 쓰레드가 CPU를 차지하고 실행중인 상태이다. 쓰레드가 차지하고 있는 CPU를 다른 쓰레드에 넘겨주기 위해서는 suspend()/sleep()/wait() 메소드들과 yield() 메소드가 있다. suspend()/sleep()/wait() 메소드들은 쓰레드를 블락상태로 전이해서 다른 쓰레드에게 CPU를 넘겨주는 형태이고, yield() 메소드는 쓰레드가 Runnable 상태로 전이해서 다른 쓰레드에 CPU를 넘겨줄 수 있다. 그런데 두 종류의 메소드에는 스케쥴링에 약간의 차이점이 있다. suspend()/sleep()/wait() 메소드들은 쓰레드가 블락되기 때문에 CPU를 다른 쓰레드에게 넘겨주지만, yield() 메소드는 Runnable 상태로 가기 때문에 현재 쓰레드가 우선 순위가 가장 높다면 다시 CPU를 잡을 수 있다는 것이다. 이렇게되면 우선 순위가 낮은 쓰레드들은 CPU를 차지해서 실행할 기회를 얻을 수 없을 수도 있다. suspend()메소드에 의해 블락된 쓰레드는 resume() 메소드를 통해 블락 상태에서 Runnable 상태로 전이 할 수 있다. 반면에 wait() 메소드에 의해 블락된 쓰레드는 notify() 메소드를 통해서 블락 상태에서 Runnalbe 상태로 전이 될 수 있다. sleep() 메소드에 의해 블락된 쓰레드는 일정 시간이 되면 자동적으로 Runnable 상태로 전이된다.

자바에서 쓰레드는 실제 운영체제의 쓰레드로 매핑이 이루어져야 실제로 쓰레드로서 작업을 할 수 있다. UNIX나 NT, Win95, OS/2 등이 쓰레드를 지원하는 방식이 다르기 때문에 자바 쓰레드와 실제 운영체제의 쓰레드와 매핑이 달라질 수 있다. 실제 운영체제에서 쓰레드는 사용자 쓰레드와 커널 쓰레드로 구분된다. 사용자 쓰레드는 사용자가 라이브러리를 이용해서 만든 쓰레드이고, 커널 쓰레드는 실제 스케쥴링이 일어나는 스케쥴링 단위가 된다. 사용자 쓰레드와 커널 쓰레드는 M-1 관계, 1-1 관계, M-N 관계를 갖을 수 있다. NT 와 Win95는 1-1 관계의 대표적인 예이고, M-N은 Solaris 쓰레드의 대표적인 예이다. 자바 쓰레드는 일종의 사용자 쓰레드이다.

M-1 매핑

자바 쓰레드는 사용자 쓰레드와 1:1 관계를 갖고, 사용자 쓰레드는 커널 쓰레드와 M:1 관계를 갖는다. 따라서, 커널 쓰레드는 하나만 있기 때문에 다중 쓰레드의 장점을 살리지 못하고 있다. M-1 매핑의 대표적인 예는 green_threads 이다. 솔라리스 2.4, 2.5 운영체제에 제공되는 JDK는 모두 green_threads를 사용하고 있다.

One-One 매핑

1:1 매핑 관계는 NT, Win95에서 사용되는 쓰레드이다. 자바 쓰레드 1개에 커널 쓰레드가 1개 매핑되기 때문에 다중 쓰레드의 장점을 잘 살릴 수 있다.

M-N 매핑

솔라리스 2.6에서 지원되는 자바 native 쓰레드를 사용하는 경우에, C 언어에서 쓰레드를 사용하는 것처럼 쓰레드를 자유롭게 사용할 수 있다. 프로그래머는 원하는 경우 사용자 쓰레드가 원하는 만큼의 커널 쓰레드와 매핑이되도록 프로그램 할 수 있다. NT의 1:1 매핑보다 프로그래머 가 프로그램하는데 편리하면서, 다중 쓰레드의 장점을 최대한 활용할 수 있다.



출처 : http://www.javastudy.co.kr/docs/jm/jm15.html

'공부방 > Java' 카테고리의 다른 글

멀티 스레드(Thread) 구현하기  (0) 2012.02.13
쓰레드의 Wait()와 notify()사용  (0) 2012.02.13
synchronized (this) ? synchronized (Class.class) ?  (0) 2012.02.13
JAVA Thread  (0) 2012.02.13
Java의 synchronized 분석  (0) 2012.02.13