§1   쓰레드란?
 프로그램을 실행시키면시작, 실행연속, 그리고 끝이라는 과정을 밟습니다. 여러분은 윈도95를 사용하면서 여러 개의 응용프로그램을 동시에 띄워놓고사용해본 경험이 있을 것이다. 이러한 것을 다중작업(multitask)이라 부르는데, 경우에 따라서는 하나의 프로그램 안에서다중작업을 수행할 수 있습니다. 이때 하나의 프로그램 안에서 실행되는 각각의 독립된 작업을 쓰레드(thread)라고 부릅니다.그러니까 쓰레드는 독립된 프로그램 역할을 하면서 혼자서는 실행될 수 없고 다른 프로그램 안에서만 실행되는 단일의 연속제어흐름으로 볼 수 있습니다. 
  독립된 프로그램 역할을 하면서 혼자서는 실행될 수 없고 다른 프로그램 안에서만 실행되는 단일의 연속 제어흐름을 쓰레드(thread)라고 한다. 

이런 의미에서 본다면 독자적으로는 실행될 수 없고 웹브라우저 안에서 독립적으로 실행되는 애플릿은 그 자체로 쓰레드를 구성한다고 볼 수 있습니다. 
하나의 프로그램 안에서 다중작업을 수행한다는 것은 그 프로그램 여러 개의 쓰레드가 포함되어있다는 것을 의미하고, 이러한 한 프로그램 안에 여러 쓰레드가 있는 것을 다중쓰레드(multithread) 라고 합니다. 

다중쓰레드 응용의 가장 대표적인 예는 웹브라우저입니다. 여러분은 넷스케이프 같은 웹브라우저를 사용하면서 그림이나 애플릿을다운받으면서, 그 HTML 페이지 안에서 나오는 사운드를 작동시켜 음악을 감상하면서, 동시에 페이지를 스크롤하여 문서 내용을읽은 경험이 있을 것이다. 이 모든 것이 다중쓰레드 덕분입니다.
 
 

§2   쓰레드 만들고 시작하기

===================================================================================================
1. Thread 클래스를 확장하여 쓰레드 만들기

  1) Thread 클래스를 확장한(extends) 새로운 클래스 만들기

  2) 쓰레드 인스턴스 생성

  3) start() 메소드 호출

 

예)

/** @File: ExThread.java */

import java.lang.*;

import java.net.*;

import java.io.*;

 

class CountThread extends Thread {

   /* 쓰레드가 해야 할 작업을 run() 메소드에 구현한다 */

   public void run() {

      for( int i=0; i<10; i++) {

        System.out.println("Thread Count: " + i );

      }

   }

}

 

public class ExThread {

   public static void main( String args[] ) {

      countThread thd_1 = new CountThread();  // 쓰레드 생성

      Thd_1. start();   // 쓰레드 시작. run()메소드를 호출하는게 아니라, start()메소드를 호출.

   }

}

 

 

2. Runnable 인터페이스를 구현하여 쓰레드 만들기

   1) Runnable 인터페이스를 구현하는 새로운 클래스를 만든다.

   2) 클래스의 인스턴스를 만든다.

   3) Thread( (Runnable)obj ) 메소드로 새로운 쓰레드 객체를 만든다. (예제 참조)

   4) start() 메소드를 호출한다.

 

예)

/** @File: ExRunnable.java */

import java.lang.*;

import java.net.*;

import java.io.*;

 

class CountThread_2 implements Runnable {

   /* 쓰레드가 해야 할 작업을 run() 메소드에 구현한다. */

   public void run() {

      for( int i=0; i<10; i++ ) {

         System.out.println("Thread Count: " + i );

      }

   }

}

 

public class ExRunnable {

   public static void main( String args[] ) {

      // 클래스의 인스턴스를 만들고,

      //Thread( (Runnable)obj ) 메소드로 새로운 쓰레드 객체를 만듦

      Thread thd_1 = new Thrad( new CountThread_2() );

 

      // 쓰레드 시작

      thd_1.start();

   }

}

===================================================================================================
 이제 쓰레드를 어떻게 만들고 또한 이를 어떻게 실행하는지에 대해 알아보겠습니다. 
새로운 실행 쓰레드를 생성하고 시작하는 데는 다음 2가지 방법이 있습니다. 

■ 모든 쓰레드의 모클래스인 Thread 클래스를 확장하기
■ Runnable 인터페이스를 이행하기 
이제 이 두 방법을 구체적으로 알아보겠습니다. 


■ Thread 클래스 확장하기
◆ 쓰레드 만들기
임의의 쓰레드 클래스는 extends Thread 절을 이용해 모든 쓰레드의 모클래스인 Thread 클래스를 확장하고, 그 몸체에 Thread 클래스의 run() 메쏘드를 덮어쓰기 합니다.  

◆ Thread 클래스 확장하는 쓰레드 클래스
class TestThread extends Thread {
   ...
   public void run() {
      // 쓰레드 이행 
      . . .
   }
}

◆ 쓰레드 시작하기
그 다음 이 쓰레드를 시작하기 위해서 다음과 같이 쓰레드를 생성하여 이를 시작하는 start() 메쏘드를 불러와 이행해야 
합니다. 
     TestThread p = new TestThread();
     p.start();

start() 메쏘드를 불러와 쓰레드를 시작하는 것이, 애플릿의 경우와 비슷하지요? 이로써 애플릿은 그 자체로 쓰레드와 밀접한 관계가 있음을 짐작할 수 있습니다.


■ Runnable 인터페이스 이행하기
◆ 쓰레드 만들기
쓰레드 클래스를 정의하는 또 다른 방법은 implements Runnable 절을 이용해 Runnable 인터페이스를 이행하고, 그 몸체에 Runnable 인터페이스의 추상 메쏘드인 run() 메쏘드를 덮어쓰기 하는 것입니다.  

◆ Runnable 인터페이스를 이행하는 클래스
class TestRun implements Runnable {
   ...
   public void run() {
      // 쓰레드 이행 
      . . .
   }
}


그러나 이는 Thread 클래스 확장하는 클래스와 달리 아직 쓰레드 클래스라고 볼 수는 없습니다. 실제 이것이 쓰레드가 되기 위해서는 한 단계를 더 거쳐야 합니다. 
  
◆ 쓰레드 시작하기
앞에서 Runnable 인터페이스를 이행하는 실행가능runnable 클래스가 쓰레드가 되기 위해서는 한 단계를 더 거쳐야 한다고 했는데, 이것은 이행 코드에서 이루어집니다. 
실행가능runnable 클래스를 쓰레드로 만들고 이를 시작하기 위해서는 다음 과정을 거칩니다. 

쓰레드화 하고자 하는 Runnable 클래스의 인스턴스 생성.
Thread 클래스의 구성자 Thread(Runnable target) 을 사용해 쓰레드 클래스 인스턴스를 쓰레드화.
start() 메쏘드를 불러와 이를 시작. 
다음은 이런 과정의 예를 보여주는 코드 조각입니다. 
TestRun p = new TestRun();
new Thread(p).start();
 

§3  어떤 방법을 택할 것인가?
그렇다면 쓰레드를 만드는 위 두 가지 방법 중에 어떤 것을 사용하는 것이 좋을까요? 
그것은 두 방법의 자바 문법적 차이에 달려있습니다. 

일반적인 경우는 Thread 클래스 확장법을 사용합니다.
쓰레드화 하고자 하는 클래스가 이미 다른 클래스를 확장하고 있을 때는 하나의 클래스가 2개의 클래스를 동시에 확장할 수 없으므로 Runnable 인터페이스 이행법을 사용합니다. 
후자와 같은 이유로 Runnable 인터페이스 이행법을 사용하는 가장 대표적인 경우가 애플릿입니다. 모든 애플릿은 Applet 클래스를 확장하기 때문에 Runnable 인터페이스 이행법을 통해 쓰레드화해야 합니다. 
 

§4   쓰레드의 주기와 제어
 또 다른 실행가능 프로그램인 애플릿이 그랬던 것처럼, 쓰레드 역시 실행가능 프로그램이라 탄생, 활동, 죽음이라는 생명주기를 갖습니다. 
보다 구체적인 쓰레드의 변화 상태는 다음과 같습니다. 
■ 탄생(생성)에 의한 새 쓰레드 상태
■ 실행가능(Runnable) 상태
■ 실행불가능(Not Runnable) 상태
■ 죽은(Dead) 상태
이 상태 간의 전환은 그에 대응되는 메쏘드 이행으로 이루어집니다. 
이에 대해 하나 하나 구체적으로 알아보겠습니다. 

 

■ 새 쓰레드 상태
새 쓰레드 상태란 생성은 되었지만 어떤 활동도 하지 않는 상태를 말합니다. 새 쓰레드를 생성하는 방법은 물론 개체  생성표현식을 통해서입니다.
TestThread p = new TestThread();
이렇게 생성된 새 쓰레드 상태는 아무 활동도 할 수 없고, start() 메쏘드를 호출해 실행가능Runnable 상태가 되어야만 활동을 할 수 있고, stop() 메쏘드를 호출하면 죽은 상태가 됩니다. 


■ 실행가능 상태
생성된 새 쓰레드를 실행가능Runnable 상태로 만들어 활동을 시키기 위해서는 생성 후 바로 start() 메쏘드를 불러내어야 
합니다. 
TestThread p = new TestThread();
p.start();


◆ start() 메쏘드는 쓰레드를 실행하는 데 필요한 시스템 자원을 생성하고, 쓰레드 실행에 필요한 스케줄을 짜며, 그리고 그 쓰레드의 run() 메쏘드를 호출합니다. 
다른 메쏘드는 직접 호출하는데 반해 쓰레드 클래스의 run() 메쏘드는 start() 메쏘드를 통해 불러내어지는 것에 주목하십시오. 
이와 같이 start() 메쏘드를 호출한 뒤의 상태를 "실행가능runnable" 상태에 있다고 하는데, "실행중running"이 아닌 "실행가능runnable" 상태라 부르는 것은 실제로 실행중인 것이 아니기 때문입니다. 

많은 컴퓨터는 단일 프로세서를 갖고 있어, 모든 "Runnable" 쓰레드를 동시에 돌리는 것이 불가능합니다. 따라서 자바실시간시스템은 모든 "Runnable" 쓰레드간에 프로세서를 분배하는 스케줄링scheduling 계획을 이행해야 합니다. 이스케줄링은 쓰레드 우선순위priority에 따라 행해집니다. 이러한 쓰레드 우선순위와 스케줄링에 대해서는 뒤에 알아보겠습니다.
 

■ 실행불가능 상태
쓰레드는 다음 3가지 사건중 하나가 발생하면 "실행불가능Not Runnable" 상태로 들어갑니다. 
누군가 그 sleep() 메쏘드를 불러낸다. (잠자기)
쓰레드가 특정한 조건이 충족될 때를 기다리기 위해 wait() 메쏘드를 사용한다. (대기)
쓰레드가 I/O 위에서 블로킹blocking하고 있다. (차단)
가령, 다음 코드의 굵은 활자 부분은 쓰레드 개체 p 를 10초(10,000 milliseconds) 동안 잠자게 합니다.
TestThread p = new TestThread();
p.start();
try {
    p.sleep(10000);
} catch (InterruptedException e){
}
쓰레드 개체 p 가 잠자고 있는 10초 동안은 프로세서가 사용가능하더라도, 실행되지 않습니다. 
10초가 지나면, p 는 다시 "실행가능" 상태가 되고, 이제 프로세서가 사용가능하면 다시 실행됩니다. 


◆ "실행불가능" 상태에서 "실행가능" 상태로 전환
"실행불가능" 상태에서 다시 "실행가능" 상태로 빠져나가기 위해서는 다음과 같은 과정을 거쳐야 합니다. 
만약 쓰레드가 휴면상태에 있으면(sleep), 지정된 시간이 경과해야 합니다. 
만약 쓰레드가 wait() 메쏘드를 이행해 특정한 조건이 충족될 때를 기다리고 있으면(대기 중이면), 다른 쓰레드가 이 대기 쓰레드에게 notify() 나 notifyAll()를 불러내어 그 조건이 충족되었음을 통보해야 한다. 
만약 쓰레드가 I/O 상에 차단되어 있으면, 이 I/O 가 마쳐야 한다. 
Deprecated: 실행불가능 상태로 만드는 방법 중에 일시중단하는 suspend() 메쏘드와 이를 다시 실행가능 상태로 전환하는resume() 메쏘드가 있었습니다. 그러나 이는 데드락을 발생시킬 가능성 때문에 1.2 버전부터 폐기되었습니다.


■ 죽은 상태
쓰레드는 자연적 원인이나 중단stop을 통해 활동을 멈추고 죽은dead 상태가 됩니다. 
◆ 자연적 죽음
자연적으로 죽는 경우는 run() 메쏘드를 정상적으로 빠져나가는 경우입니다. 
아래 run() 메쏘드는 while 순환문에 의해 100번 반복한 뒤에 빠져나가 그 쓰레드는 죽은 상태가 됩니다. 

public void run() {
    int i = 0;
    while (i < 100) {
        i++;
        System.out.println("i = " + i);
    }
}

 

◆ 강제적 죽음
강제로 쓰레드를 죽이기 위해서는 stop() 메쏘드를 불러내어야 합니다. 
다음 코드조각은쓰레드 p 를 생성하여 활성화시킨 후, 현재 쓰레드currentThread()를 10초간 휴면상태로 둡니다. 그 다음 현재쓰레드가 깨면 (10초가 지나면), stop() 메쏘드를 불러내어 쓰레드 p 를 죽이는 과정을 보여줍니다.
 

TestThread p = new TestThread();
p.start();
try {
    p.sleep(10000);
} catch (InterruptedException e){
}
p.stop();
Deprecated : 그러나 강제적 쓰레드 죽이기가 갖는 근본적 문제로 이 방법은 1.2 버전부터 제거되었습니다. 즉, Thread의 stop() 메쏘드는 폐기되었습니다. 
 

쓰레드군
 ■ 쓰레드군 개념
◆ 쓰레드군의 정의
쓰레드군(thread group)은 쓰레드들의 집합을 말합니다. 하나의 원소만 갖는 집합도 있듯이, 단일 쓰레드만 포함하는 쓰레드군도 있습니다. 따라서 모든 쓰레드는 반드시 하나의 쓰레드군에 속해야 합니다. 
여러 개의 쓰레드들을 하나의 집단으로 묶은 것을 쓰레드군(thread group)이라 한다. 

◆ 쓰레드군의 구조
쓰레드군은 다른 쓰레드군을 포함할 수도 있습니다. 쓰레드군들은 초기 쓰레드군을 제외한 모든 쓰레드군들이 하나의 부모를 갖는 트리구조를 갖습니다.
아래 <그림 2.1.1>은 이러한 쓰레드군의 구조를 도식적으로 보여줍니다. 
 


2.1.1 쓰레드군 구조의 도식적 표현

 


 

  
  
 

◆ 쓰레드군의 용도
쓰레드군은 다중 쓰레드들을 하나의 개체로 묶어 동시에 처리할 수 있도록 할 때 사용합니다.
◆ 쓰레드군에 속한 쓰레드의 접근 범위
임의 쓰레드는 그 자신이 속한 쓰레드군에 대한 정보에는 접근할 수 있지만, 그 쓰레드군의 부모격인 쓰레드군이나 다른 쓰레드군에 대한 정보에는 접근할 수 없습니다.
◆ 현재 쓰레드와 현재 쓰레드군
다중쓰레드가 작동하고 있는 경우 현재 활성화되어 작동 중인 쓰레드를 현재 쓰레드current thread라 하고, 그것이 속한 쓰레드군을 현재 쓰레드군current thread group이라 부릅니다.
 

◆ 쓰레드군의 명칭
모든 개체가 명칭을 가져야 하는 것처럼, 쓰레드군 역시 명칭을 가져야 그것을 다룰 수 있을것입니다. 사실 애플리케이션은 그 자체로 하나의 쓰레드이며 동시에 쓰레드군을 이룹니다. 그런데 애플리케이션의 쓰레드군 명칭은프로그래머가 임의로 붙이는 것이 아니라, 실시간 시스템이 자동으로 main 이라는 쓰레드군 명칭을 붙입니다. 이 명칭은 모든애플리케이션은 main() 메쏘드를 갖는 것을 기억하면 쉽게 이해할 수 있을 것입니다.
◆ 디폴트 쓰레드군
만약새 쓰레드를 생성하면서 그것이 속할 쓰레드군을 구체적으로 지정하지 않았다면, 실시간 시스템은 자동적으로 그 쓰레드를 생성한쓰레드군, 즉, 현재 쓰레드군에 속하도록 합니다. 이와 같이 쓰레드군을 구체적으로 지정하지 않은 쓰레드가 속하는 쓰레드군을디폴트 쓰레드군default thread group이라 부릅니다. 
가령 애플리케이션 위에서 쓰레드를 생성했다면 그것은 디폴트 쓰레드군은 main 에 속합니다.
 

■ 쓰레드군 만들기
쓰레드군을 만드는 것은 단순히 ThreadGroup 개체를 생성하는 것으로 끝납니다. java.lang.ThreadGroup 클래스에는 다음 두 구성자가 포함되어 있습니다.
 
public ThreadGroup(ThreadGroup parent, String name)
여기서 인자 name 은 생성할 쓰레드군의 명칭을 지정합니다. 인자 parent 는 쓰레드군의 트리 구조상 새 쓰레드군의 부모 쓰레드군을 지정합니다.
public ThreadGroup(String name)
여기서 인자 name 은 생성할 쓰레드군의 명칭을 지정합니다. 부모 쓰레드군의 지정이 없으므로 현재 쓰레드군이 부모 쓰레드군이 됩니다.
가령 쓰레드군 생성표현식
new ThreadGroup(father_threadgroup,  son_threadgroup)
을 사용하면, 부모 쓰레드군 father_threadgroup 아래 son_threadgroup 을 생성합니다.
쓰레드군 생성표현식
new ThreadGroup(new_threadgroup)
은 현재 쓰레드군 아래 new_threadgroup 을 생성합니다.
 

Thread 의 사라진 메쏘드들
쓰레드의 주기를 제어하기 위한 Thread 클래스의 일부 메쏘드들이 시스템의 안정을 방해하는 요소가 있어 1.2버전부터 폐기되었습니다. 이에 대해 알아봅니다. 
stop() 메쏘드 
stop() 메쏘드를 이용해 쓰레드를 강제적으로 멈추면 예상치 못한 결과가 발생할 위험이 있어 1.2버전부터 폐기되었습니다. 
즉,쓰레드를 강제적으로 멈추면 그 쓰레드가 잠궈두었던locked 모니터들이 모두 풀리게 됩니다. 그러면 이 모니터들이 감시하고 있던개체중 일부가 불일치 상태로 놓이게 되고, 이때 어떤 다른 쓰레드가 이 개체를 보면 불일치 상태에서 보게 됩니다. 이와 같은개체를 손상된 개체damaged object라 부르는데, 만약 쓰레드가 손상된 개체에 작용하면 예상치 못한 결과가 발생할 수있습니다. 다른 미검사 예외들과는 ThreadDeath 예외는 특별한 경고없이 쓰레드를 조용히 죽이기 때문에, 어떤 개체가손상된 것을 한참 시간이 지난 뒤에나 알게됩니다. 
소프트웨어적으로 이러한 문제를 해결할 수는 있으나 너무 복잡하여 실용성이 없고, 따라서 위험을 내포한 stop() 메쏘드는 폐기시켜 버렸습니다

suspend() 와 resume() 메쏘드
suspend() 메쏘드 원래부터데드락deadlock을 발생시킬 가능성을 지니고있습니다. 만약 대상 쓰레드가 일시중단 중에 중요 시스템 자원을 보호하고 있는모니터의 락을 유지하고 있으면, 이 대상 쓰레드가 재시작하기 전에는 다른 쓰레드가 이 자원에 접근할 수 없습니다. 또한 이 대상쓰레드를 재시작하게 하는 쓰레드가 resume() 메쏘드를 호출하기 전에 이 모니터에 락을 걸고자 하면, 데드락이 발생합니다.이런 데드락은 전형적으로 "얼어붙은frozen" 프로세스로 나타납니다.
 

 

--------------------------------------------------------------------------------
 
쓰레드 클래스

§1   Runnable 인터페이스
 java.lang.Runnable 인터페이스는 쓰레드를 실행할 클래스가 이행해야 하는 인터페이스로, 이 Runnable 인터페이스를 이행하는 클래스에는 인자없는 run() 메쏘드의 이행을 구체적으로 정의해야 합니다.
이인터페이스는 활성화된active 개체가 코드를 실행하고자 할 때 필요한 공통 프로토콜을 제공합니다. 여기서 활성화되었다는 것은쓰레드가 시작되어 아직 중지하지 않은 상태를 말합니다. Runnable 을 이행하는 대표적인 클래스는 Thread 클래스입니다.

또한 Runnable 은 어떤 클래스가 Thread 의 부분클래스가 아니면서 활성화되도록, 즉, 쓰레드 역할을 하도록 해줍니다. (앞의 "Runnable 인터페이스를 이행한 쓰레드 만들기"를 참조하십시오.)
 

§2  ■ run() 메쏘드
run() 메쏘드는 Runnable 인터페이스가 갖는 유일한 메쏘드입니다. 
 
public void run()
Runnable인터페이스가 갖는 유일한 추상메쏘드로, 이 인터페이스를 이행하는 쓰레드 역할을 할 클래스는 이 메쏘드를 덮어쓰기 하여 이행방법을 구체적으로 정의해야 합니다. (인터페이스의 모든 메쏘드는 별 다른 말이 없어도 디폴트로 추상메쏘드임을 기억하십시오.)
TestRun클래스가 Runnable 인터페이스를 이행하는 쓰레드화 할 클래스인 경우, 다음과 같이 이 Runnable 클래스의 인스턴스를생성하고, 이를 쓰레드화 한 후, 그 쓰레드의 start() 메쏘드를 불러내면, 이 start() 메쏘드는 TestRun클래스에 정의된 run() 메쏘드를 호출하여, 그 쓰레드를 구체적으로 이행합니다. 
 
TestRun p = new TestRun();
new Thread(p).start();
 

§3   Thread 클래스
 모든 쓰레드 클래스의 모클래스격인 java.lang.Thread 클래스는 쓰레드가 가져야할 기본적 요소들이 정의되어 있습니다.
■ Thread 클래스 선언부
Thread 클래스의 선언부는 다음과 같습니다.
public class Thread implements Runnable
Runnable 인터페이스를 이행하는 것에 주목하십시오.
■ 상수
Thread 클래스에는 쓰레드가 갖는 우선순위priority를 지정하는 다음 3개의 상수가 정의되어 있습니다. 
 
public static final int MIN_PRIORITY
public static final int MAX_PRIORITY
public static final int NORM_PRIORITY
이들은 각기 최소 우선권, 최대 우선권, 디폴트 우선권을 지정하는 상수입니다.
 

§4  ■ 구성자
public Thread(ThreadGroup group, Runnable target, String name)
인자로 주어진 실행가능Runnable 개체(쓰레드화 할 대상 클래스 개체) target을 인자로 주어진 쓰레드군 group 에 속하는 명칭 name의 새로운 쓰레드 개체를 할당(생성)합니다.
 
인자 group 이 null 값으로 주어지면 (즉, 쓰레드군이 지정되지 않으면) 새로이 생성된 쓰레드는 현재 쓰레드군(디폴트 쓰레드군)에 속합니다.
새로이 생성된 쓰레드가 시작할 때, 
인자 target 이 null 값이 아니면 (즉, 쓰레드화 할 대상 Runnable 개체가 지정되면), 이 target 의 run() 메쏘드가 호출됩니다.
인자 target 이 null 값이면, 현재 쓰레드의 run() 메쏘드가 호출됩니다.


§5  새로이 생성된 쓰레드의 우선순위priority는 그것을 생성한 쓰레드(즉, 현재 실행중인 쓰레드)의 우선순위와 같도록 지정됩니다. 우선순위값을 새로 지정하고자 하면 setPriority() 메쏘드를 사용합니다.
새로이 생성된 쓰레드가 초기에 데몬daemon 쓰레드로 표시되면 그것을 생성한 쓰레드 역시 현재 데몬 쓰레드로 표시되어 있고, 이 역도 성립합니다. 쓰레드를 데몬으로 지정하고자 하면 setDaemon() 메쏘드를 사용합니다.


 데몬
데몬daemon이란 어떤 서비스 요청에 대해 응답을 주기 위해 장시간 작동하는 백그라운드 프로세스를말합니다. 일반적으로 데몬 프로그램은 그 용어 뒤에 "d"로 끝납니다. 대표적으로 여러분들이 많이 들어본 데몬은 가령 웹서버프로그램으로 사용되는 http 데몬, 즉, httpd 입니다. 
쓰레드를 데몬으로 지정한다는 것은 그 쓰레드를 장시간 작동하는 백그라운드 프로세스로 만든다는 것을 의미합니다.
 

§6  public Thread(Runnable target, String name)
이는 Thread(null, target, name) 와 같은 효과를 갖습니다. 즉, 생성할 쓰레드의 쓰레군을 디폴트 쓰레드군으로 지정하고자 할 때 사용합니다.
public Thread(ThreadGroup group, String name)
이는 Thread(group, null, name) 와 같은 효과를 갖습니다. 즉, 별도로 쓰레드화 할 Runnable 개체가 있는 것이 아니라, 이 구성자를 포함하는 클래스 자체를 쓰레드화 하고자 할 때 사용합니다.
public Thread(String name)
이는 Thread(null, null, name) 와 같은 효과를 갖습니다. 


§7  public Thread(ThreadGroup group, Runnable target)
이는프로그래머가 별도의 쓰레드 명칭을 부여할 의도가 없을 때 사용합니다. 이 경우 새로이 생성된 쓰레드는 시스템에 의해Thread-"+n 형태의 명칭이 자동적으로 부여됩니다. 여기서 n 생성된 쓰레드의 순서대로 붙는 정수입니다.
public Thread(Runnable target)
쓰레드화 할 대상 Runnable 개체만 지정하고, 디폴트 쓰레드군과 자동 쓰레드명을 사용하고자 할 때 사용합니다.
public Thread()
어떠한 지정없이 이 구성자를 포함하는 클래스 자체를 쓰레드화 하여, 디폴트 쓰레드군에 속하도록 하고, 자동 쓰레드명을 사용하고자 할 때 사용합니다. 


§8  ■ 메쏘드
◆ 쓰레드 제어 메쏘드
public void start()
쓰레드가 실행을 시작하도록 합니다. 실제 쓰레드의 이행은 이 start() 메쏘드가 run() 메쏘드를 불러냄으로써 이루어집니다.
public void run()
현재 쓰레드의 start() 메쏘드에 의해 불러내어져 실질적으로 쓰레드를 이행합니다. 
Thread 의 모든 부분클래스는 이 run() 메쏘드들 덮어쓰기 하여 그 이행을 구체적으로 정의해야 합니다.
public static void yield()
현재 실행중인 쓰레드 개체를 일시 중단하고 다른 쓰레드가 실행되도록 합니다. 


§9  public static void sleep(long millis) throws InterruptedException
현재 실행중인 쓰레드가 지정된 시간 (단위는 밀리초) millis 동안 일시 중단하도록 합니다. 그렇다고 모니터의 소유권을 잃는 것은 아닙니다. 
(모니터와 락lock에 대해서는 다음 시간 소개)
public static void sleep(long millis, int nanos)
                  throws InterruptedException
현재 실행중인 쓰레드가 지정된 시간 millis 밀리초와 nanos 나노초 동안 일시 중단하도록 합니다.
public void interrupt()
현재 쓰레드를 가로채기 합니다.
public final void join() throws InterruptedException
현재 쓰레드가 죽을 때까지 기다립니다. (연기서 현재 쓰레드란 현재 실행중인 쓰레드가 아니라, 프로그램 코드상 현재 생성된 쓰레드 개체를 말합니다.)
public final void join(long millis)
public final void join(long millis, int nanos)
                throws InterruptedException
현재 쓰레드가 죽을 때까지, 단 최대 millis 밀리초까지만 [millis 밀리초와 nanos 나노초까지만] 기다립니다.


§10  ◆ java.lang.Object 클래스로부터 상속받은 쓰레드 제어 메쏘드
다음은 Thread 클래스에 포함된 것이 아니라 모든 클래스의 뿌리 클래스인 java.lang.Object 클래스로부터 상속받은 쓰레드 제어 메쏘드들입니다. 
 
public final void wait() throws InterruptedException
다른 쓰레드가 현 개체(쓰레드가 접근할 대상 개체)에 대해 notify() 나 notifyAll() 메쏘드를 불러낼 때까지 현재 실행중인 쓰레드를 대기하게 합니다.
public final void wait(long timeout)
                throws InterruptedException
적어도 파라미터로 지정된 timeout (밀리초) 동안 대기한다는 점을 제외하면, wait() 와 기능이 같습니다. 
 

§11  public final void wait(long timeout, int nanos)
                throws InterruptedException
대기 시간을 나노초까지 정확히 지정할 수 있다는 점을 제외하면 wait(long timeout) 와 기능이 같습니다.
public final void notify() 
현 개체의 모니터를 기다리고 있는 쓰레드 중 하나를 무작위로 선택해 깨웁니다.
public final void notifyAll() 
현 개체의 모니터를 기다리고 있는 모든 쓰레드를 깨운다는 점을 제외하면 notify() 와 동일합니다. 


§12  ◆ 쓰레드 상태 관련 메쏘드
public final void setPriority(int newPriority)
현재 쓰레드의 우선순위를 파라미터로 전달한 값으로 바꿉니다.
public final int getPriority()
현재 쓰레드의 우선순위를 되돌립니다.
public final void setName(String name)
현재 쓰레드의 명칭을 파라미터로 전달한 명칭으로 바꿉니다.
public final String getName()
현재 쓰레드의 명칭을 되돌립니다.
public static Thread currentThread()
현재 실행중인 쓰레드 개체에 대한 참조값을 되돌립니다.


§13  public final boolean isAlive()
현재 쓰레드가 활성상태인지 어떤지 검사합니다. 활성상태란 일단 시작start을 했으면서 아직 죽지 않은 상태를 말합니다.
public static boolean interrupted()
현재 쓰레드가 가로채기 당한 적이 있는지 검사합니다. 가로채기 당한 쓰레드에 대해 이 메쏘드를 불러내면 다시 깨끗한 상태가 됩니다.
public boolean isInterrupted()
인스턴스 메쏘드란 점과 이 메쏘드를 불러낸 이후에도 쓰레드 상태가 변하지 않는다는 점을 제외하면 interrupted() 메쏘드와 기능이 같습니다.
public final ThreadGroup getThreadGroup()
현재 쓰레드가 속한 쓰레드군을 되돌립니다.
public static int activeCount()
현재 쓰레드가 속한 쓰레드군 안에서 현재 활성상태에 있는 쓰레드들의 수를 되돌립니다.


====================================================================================================================

 

쓰레드 구현


§1   쓰레드의 필요성
 쓰레드는 독립된 프로그램 역할을 하면서 혼자서는 실행될 수 없고 다른 프로그램안에서만 실행되는 단일의 연속 제어흐름이라 했습니다. 그렇다면 왜 이런 것이 필요한지, 그리고 어떤 경우 쓰레드를 사용해야하는지 예를 통해 알아봅시다. 
여러분은 이미 Test_Box 애플릿을 이용해 단답형 문제를 통한 자기 테스트를 해보았을 것입니다. 이 애플릿에는 정해진 시간동안 타이머가 작동하고, 그 시간 안에 답안을 기입해야 합니다. 
그런데 다음 시험 애플릿을 실행해보고 어떤 일이 벌어지는지 학인해봅시다.
 

§3   생산자/소비자 시나리오
 우리는 다중쓰레드의 특성들을 공부하기 위해 소위 생산자/소비자 시나리오(producer/consumer scenario)라는 문제를 샘플로 제시합니다. 
생산자/소비자 시나리오란 생산자가 무언가(가령 데이터)를 제공하면 소비자가 이를 받아 사용하는 관계와 과정을 말합니다. 잘알다시피 일반적으로 생산자는 직접 생산품을 소비자에게 전하지 않고 중개인(가령 판매자)을 거치게 됩니다. 

그런데 생산자와 소비자는 (비록 중개인을 매개로 하긴 하지만) 서로 독립적으로 활동하지만 그 생산 및 소비가 동시에이루어집니다. 공간적으로 서로 분리되어 있으면 각기 서로 다른 자원을 사용하는 실세계에서는 전혀 문제가 없지만, 하나의 CPU를 통해 그 활동(실행)이 이루어지는 컴퓨터에 있어서는 생산자와 소비자가 서로 독립적으로 그러나 동시에 실행하기 위해서는다중쓰레드를 사용할 수밖에 없습니다.
 

§4  ■ 문제 제시
우리는 생산자/소비자 시나리오에 따른 다중쓰레드의 특성들을 공부하기 위해 다음과 같은 문제를 제시합니다. 
생산자가 중개인에게 0에서 9까지 수를 제공하고, 소비자는 중개인으로부터 이 수들을 넘겨받습니다. 이 과정들이 순차적으로 진행되는 프러시저로가 아니라, 동시에 이루어지도록 하는 것이 우리의 목적입니다. 
 
 
2.3.1  생산자/소비자 시나리오
 

§5   다중쓰레드로 인해 발생하는 문제
 일반적으로 다중쓰레드는 CPU 를 포함해 한정된 자원을 서로 공유해야 하는 경우가 허다하게 발생합니다. 여기서는 이와 같이 다중쓰레드가 자원을 공유함으로써 발생하는 현상들에 대해 알아봅니다. 
■ 경쟁상태
두 쓰레드가 하나의 자원을 공유함으로써 생기는 가장 일반적인 현상이 경쟁상태입니다. 경쟁상태race condition란 여러 쓰레드가 공동 자원을 서로 먼저 소유하고자 다투는 현상을 말합니다. 
다음 생산자/소비자 시나리오 문제를 통해 어떤 경우 경쟁상태가 발생하는지 알아보겠습니다.


§6  ◆ 프로그램 작성
앞에서 제시한 생산자/소비자 시나리오 문제처럼, 생산자가 0에서 9까지 수를제공하면, 중개자가 in() 메쏘드를 통해 이를 받아 가지고 있고, 소비자는 중개자의 out() 메쏘드를 불러내어 이를 넘겨받는것입니다. 이때, 이러한 과정이 어떻게 순서적으로 이루어지는지 알아보기 위해 생산자와 소비자는 수를 넘기고 받을 때 그 수를출력하도록 합니다. 
그러면 우선 중개자 클래스부터 보겠습니다.
 
 /* 중개자 클래스 */
public class Intermediator_1 {
    private int contents;
    public void in(int value) {
        contents = value;
    }

    public int out() {
        return contents;
    }
}
 

§7  이 중개자 클래스 Intermediator_1 는 다음 필드변수와 메쏘드들을 갖습니다.
private int contents;
생산자가 제공한 int 현 값을 저장할 변수 역할을 합니다.
in(int value)
생산자가 인자 value 로 정수형 값을 제공하면 이를 필드변수 contents 에 저장합니다.
out()
소비자가 이 메쏘드를 불러내면 생산자가 제공했던 값 contents 를 되돌립니다. 


§8  이제 생산자 클래스를 보겠습니다. 

   /* 생산자 클래스 */
public class Producer_1 extends Thread {
    private Intermediator_1 inter;
    public Producer_1(Intermediator_1 inter) {
        this.inter = inter;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            inter.in(i);
            System.out.println("Producer put: " + i);
        }
    }
}
 

§9  이 생산자 클래스 Producer_1 의 특성은 다음과 같습니다.
public class Producer_1 extends Thread
클래스 선언문을 통해 이 생산자 클래스 Producer_1 은 Thread 의 부분클래스로 쓰레드 개체를 만드는 대상이 됨을 지정합니다.
private Intermediator_1 inter;
중개자 Intermediator_1 형 필드변수 inter 를 정의합니다.
public Producer_1(Intermediator_1 inter) {
        this.inter = inter;
 }
이 구성자Constructor는 Intermediator_1 형 inter
를인자로 받아 필드변수 inter 에 대입합니다. 잘 알고 있겠지만 this.inter = inter 에서 this.inter 는현 개체, 즉, 개체의 인스턴스변수임을 나타내고, 뒤의 inter 는 파라미터로 전달받는 개체형 값을 말합니다. 
이와 같은 표현식은 구성자를 통해 인스턴스변수의 값을 전달받는 전형이므로 잘 기억하십시오. 

§10  public void run() {
    for (int i = 0; i < 10; i++) {
       inter.in(i);
       System.out.println("Producer put: " + i);
    }
  }
이미 "쓰레드 만들고 시작하기"에서 말한 바와 같이 이 run() 메쏘드는 쓰레드 역할을 할 클래스가 반드시 가지고 있어야 할 메쏘드로, 그 쓰레드의 실행을 정의합니다. 
for-문을 통해 정수형 지역변수 i 를 0에서 9까지 중개자 개체 inter 의 인스턴스메쏘드 in() 의 인자로 전달합니다. 
시스템 출력문 System.out.println(...) 으로 이 정수값이 전달되는 순서, 혹은 이 Producer_1 쓰레드가 실행되는 시간을 알아봅니다. 

여기서 이 for-문이 끝나면 이 Producer_1 쓰레드는 자동적으로 죽은상태가 되는 것을 기억하십시오. (2.1절의 죽은 상태 참조)
 

§11  이제 소비자 클래스를 보겠습니다. 
 /* 소비자 클래스 */
public class Consumer_1 extends Thread {
    private Intermediator_1 inter;
    public Consumer_1(Intermediator_1 inter) {
        this.inter = inter;
    }

    public void run() {
        int value = 0;
        for (int i = 0; i < 10; i++) {
            value = inter.out();
            System.out.println("Consumer got: " + value);
        }
    }
}


§12  이 소비자 클래스 Consumer_1 는 생산자 클래스 Producer_1 와 거의 비슷하고 쓰레드 이행방법만 조금 다릅니다. 
public void run() {
    int value = 0;
    for (int i = 0; i < 10; i++) {
       value = inter.out();
       System.out.println("Consumer got: " + value);
    }
 }
중개자로부터 전달받은 값을 저장할 int 형 지역변수 value 를 정의하고, 0 으로 초기화합니다. 
for-문을 통해 중개자 개체 inter 의 필드로 저장된 값을 10번 전달받습니다. 
그때마다 매번 시스템 출력문 System.out.println(...) 으로 전달받은 값을 출력하여 이 Consumer_1 쓰레드가 실행되는 시간을 알아봅니다. 


§13  이제 이 생산자/소비자 프로그램을 실행할 주프로그램을 보겠습니다. 
 /* 생산자/소비자 시험 프로그램 */
public class Test_2_3_1 {
    public static void main(String[] args) {
        Intermediator_1 i = new Intermediator_1();
        Producer_1 p = new Producer_1(i);
        Consumer_1 c = new Consumer_1(i);
        p.start();
        c.start();
    }
}
 이 주프로그램은 쓰레드 시작하기의 정형적인 코드이므로 별 다른 설명이 필요없을 것입니다. 2.1절의 "쓰레드 만들고 시작하기"를 다시 한번 복습하십시오.


§14  ◆ 프로그램 실행
이제 이 생산자/소비자 시나리오 프로그램을 실행하여 쓰레드의 실행과정을 보겠습니다. 
위의 소스코드들을 차례대로 컴파일한 후 DOS 명령행에서 (Windows 95/98의 경우) 주프로그램 Test_2_3_1 을 실행해보십시오. 
이 결과는 실행할 때마다 매번 다르게 나오므로 여러 번 실행하여 그 변화를 눈여겨 보십시오. 아래에 그 실행결과 중 하나를 보입니다. 
  
  c:\javafactory>java Test_2_3_1
Consumer got: 0
Consumer got: 0
Consumer got: 0
Producer put: 0
Consumer got: 0
Producer put: 1
Consumer got: 1
Producer put: 2
Consumer got: 2
Producer put: 3
Consumer got: 3
Producer put: 4
Consumer got: 4
Producer put: 5
Consumer got: 5
Producer put: 6
Consumer got: 6
Producer put: 7
Producer put: 8
Producer put: 9
c:\javafactory>
 

§15  이 결과를 보면 생산자 Producer 가 미처 생산을(수를 전달) 하기도 전에 소비자 Consumer 가 소비를 하고 있습니다. 그 결과 초기화 값인 0 을 그대로 출력합니다. 
이와 같이 질서 없이 서로 다투어 실행권을 가져가는 것을 경쟁상태race condition라 부릅니다. 
이러한 경쟁상태를 없애고 질서를 유지하기 (시간적으로 전후 관계가 맞도록 하기) 위해서는 쓰레드 동기화란 기술이 필요합니다. 이에 대해서는 뒤에 알아봅니다. 


§16  ■ 기아상태와 데드락
다중쓰레드로 인해 발생하는 가장 일반적인 현상이 경쟁상태이지만, 이 보다 더 치명적인 현상이 기아상태와 데드락입니다. 
기아상태starvation란 다중쓰레드에 있어 어떤 쓰레드가 다른 쓰레드에 의해 자원에 대한 접근이 차단되어 더 이상 실행을 할 수 없는 상태를 말합니다. 
데드락deadlock은 기아상태의 극단적인 경우로 둘 이상의 쓰레드가 충족할 수 없는 조건을 기다릴 때 발생합니다. 가령 두사람이 너가 말하기 전에 나는 말하지 않겠다고 한다면 이는 결코 실형될 수 없는 조건을 기다리는 경우입니다. 이런 경우는데드락이 발생합니다. 이런 데드락은 무한루프처럼 시스템다운을 야기할 수도 있습니다.

--------------------------------------------------------------------------------

쓰레드 구현

--------------------------------------------------------------------------------
 
§17   스케줄링과 쓰레드 우선순위
 앞에서 말한 바와 같이, 다중쓰레드가 여러 프로세스를 동시에 실행하는 것이라 하였지만 실제로는 하나의 CPU 를 여러프로세스(쓰레드)가 서로 번갈아가며 사용합니다. 따라서 시스템은 모든 "Runnable" 쓰레드들이 하나의 프로세서를 적당히시간을 나누어가며 사용하도록 관리할 필요가 있습니다. 이와 같이 하나의 자원을 여러 쓰레드들이 동시에 사용하도록 시간을 배분하는정책을 스케줄링scheduling이라 합니다.
  하나의 자원을 여러 쓰레드들이 동시에 사용하도록 시간을 배분하는 정책을 스케줄링scheduling이라 한다. 

또한 이러한 스케줄링에 있어 시스템 자원(특히 CPU) 사용의 순서를 정할 필요가 있습니다. 이러한 쓰레드들의 시스템 자원 사용 순서를 쓰레드우선순위priority라 합니다.
  쓰레드들이 하나의 시스템 자원을 사용하기 위해 정한 순서를 쓰레드우선순위priority라 한다. 


§18  ■ 자바의 스케줄링 방법
스케줄링 방법은 곧 우선순위 결정방법과 동일합니다. 자바가 채택한 스케줄링(우선순위 결정) 방법은 소위 말하는 고정 우선순위 스케줄링fixed priority scheduling이라는 것입니다.
자바의 고정 우선순위 스케줄링은 구체적으로 다음과 같습니다.

1. 새로이 생성된 쓰레드는 그 쓰레드를 생성한 쓰레드로부터 우선순위를 상속받는다. 즉, 부모 쓰레드와 자식쓰레드는 동일한 우선순위를 갖는다.
2.이미 생성된, 따라서 우선순위가 주어진 쓰레드라도 setPriority() 메쏘드를 이용해 언제라도 우선순위를 바꿀 수 있다.이 우선순위는 Thread 클래스의 MIN_PRIORITY 와 MAX_PRIORITY 사이의 정수로 지정되는데, 값이 클수록우선순위가 높다.
 

§18  3. 주어진 시간에 실행가능runnable 쓰레드들 중에 우선순위가 가장 높은 쓰레드를 먼저 실행한다.
4. 이 높은 우선순위의 쓰레드가 실행을 멈추면(not runnable 상태가 되면), 그 다음 우선순위의 쓰레드를 실행한다.
5. 실행가능runnable 쓰레드가 모두 동일한 우선순위이면, 그 중 하나를 무작위로 택해 아래의 상황이 발생할 때까지 실행한다.
더 높은 우선순위의 쓰레드가 실행가능runnable 상태가 된다.
시스템이 설정한 시간나누기time-slicing에 따라, 그 쓰레드에 할당된 시간이 지났다.
그 쓰레드가 실행을 멈춘다.
앞의 조건에서 알 수 있는 것처럼, 어떤 상황에서도 우선순위가 가장 높은 실행가능 쓰레드는 항상 실행 최우선권을 갖습니다. 이런 상황을 최우선순위 쓰레드는 다른 쓰레드에 대해 선점권을 갖는다preemptive고 말합니다. 


§20  ■ 우선순위 사용의 예
여기서는 앞에서 제시한 생산자/소비자 시나리오의 문제에 우선순위를 적용하여, 이 우선순위가 어떤 결과를 내는지 알아보겠습니다. 
◆ 이기적 쓰레드
먼저 두 개의 쓰레드에 서로 다른 우선순위를 부여했을 때 일어나는 현상을 알아보겠습니다. 
아래는 앞의 경쟁상태의 생산자/소비자 시나리오 문제를 약간 수정하여 소비자에게 더 높은 우선순위를 부여한 것입니다. 
다음 주프로그램 Test_2_3_2 외 다른 소스코드는 변화가 없습니다.


§21  /* 생산자/소비자 시험 프로그램 - 불평등 우선순위 부여 */
public class Test_2_3_2 {
    public static void main(String[] args) {
        Intermediator_1 i = new Intermediator_1();
        Producer_1 p = new Producer_1(i);
           p.setPriority(2);
        Consumer_1 c = new Consumer_1(i);
           c.setPriority(3);
        p.start();
        c.start();
    }
}
 여기 보이는 굵은 활자 부분처럼, 생산자는 우선순위 2를 소비자에게는 우선순위 3을 부여하여 소비자가 더 높은 우선순위를 가지도록 하였습니다. 
그 결과가 어떻게 나타나는지 다음 실행화면을 봅시다. 


§22   
  c:\javafactory>java Test_2_3_2
Consumer got: 0
Consumer got: 0
Consumer got: 0
Consumer got: 0
Consumer got: 0
Consumer got: 0
Consumer got: 0
Consumer got: 0
Consumer got: 0
Producer put: 0
Producer put: 1
Producer put: 2
Producer put: 3
Producer put: 4
Producer put: 5
Producer put: 6
Producer put: 7
Producer put: 8
Producer put: 9
c:\javafactory>


§23  이 정도의 소비자라면 성급한 정도를 지나 제정신 아닌 소비자이겠지요. 이렇게 성급한 소비자라면 생산자 입장에서 보면 봉이겠지요. 
이와 같이 우선순위가 높은 쓰레드가 자신만 자원을 사용하고 양보하지 않을 경우, 이 쓰레드를 이기적 쓰레드selfish thread 라 부릅니다. 
이러한 이기적 쓰레드가 장시간 혹은 무한시간동안 자원을 점유하면 기아상태가 벌어집니다. 
 자바는 쓰레드의 우선순위를 어느 정도 감안하기는 하지만 기아현상 방지를 위해 이 우선순위를 절대적으로 지키지는 않습니다. 즉,비록 우선순위가 낮은 쓰레드라도 장시간 실행이 차단되는 것을 막기 위해 실행 기회를 제공합니다. 앞의 이기적 쓰레드 예는 사실매우 짧은 시간( 수십 밀리초 정도) 동안 발생한 것으로 사실 극단적인 경우입니다. 장시간 실행되는 다중쓰레드의 경우는스케줄링에 의해 기아현상을 방지합니다.
더구나 Windows 95/98 과 같은 경우는 시간나누기 time-slicing를 지원하여, 어느 한 쓰레드가 CPU 를 독점하는 것을 막고 있습니다. 


§24  ◆ 동등한 우선순위의 쓰레드
이제 동일한 우선순위를 부여하면 어떻게 되는지 알아보겠습니다. 
다음 프로그램 Test_2_3_3 은 앞의 Test_2_3_2 에서 생산자와 소비자 모두에게 동일한 우선순위 2를 부여하도록 고친 것입니다. 굵은활자 부분을 눈여겨보십시오. 
 /* 생산자/소비자 시험 프로그램 - 동등한 우선순위 부여 */
public class Test_2_3_3 {
    public static void main(String[] args) {
        Intermediator_1 i = new Intermediator_1();
        Producer_1 p = new Producer_1(i);
           p.setPriority(2);
        Consumer_1 c = new Consumer_1(i);
           c.setPriority(2);
        p.start();
        c.start();
    }
}
 이의 실행 결과는 약간의 경쟁상태는 있지만 상당히 질서있고 공정한 결과를 보여줍니다. 


§25   
  c:\javafactory>java Test_2_3_3
Producer put: 0
Consumer got: 0
Producer put: 1
Consumer got: 1
Producer put: 2
Consumer got: 2
Producer put: 3
Consumer got: 3
Producer put: 4
Producer put: 5
Producer put: 6
Consumer got: 4
Producer put: 7
Consumer got: 7
Producer put: 8
Consumer got: 8
Producer put: 9
Consumer got: 9
Consumer got: 9
Consumer got: 9
c:\javafactory>
 

§27   공정한 쓰레드 이행
 앞에서 두 쓰레드의 우선순위를 동등하게 부여함으로써 어느 정도 공정한 자원 공유를 할 수 있었습니다. 
여기서는 우선순위 지정없이 Thread 클래스의 쓰레드 제어 메쏘드를 이용해 서로 공평하게 자원을 공유하는 방법에 대해 알아봅니다. 
◆ yield() 메쏘드 사용
yield 는 말 그대로 현재 실행중인 쓰레드가 그 실행권을 다른 쓰레드에게 일시적으로 양보하는 것입니다. 
yield() 메쏘드를 사용한 다음 생산자/소비자 시나리오 프로그램을 보십시오. 
주프로그램은 Test_2_3_1 와 동일하게 우선순위가 지정되지 않았습니다. 
 /* 생산자/소비자 시험 프로그램 - 소비자 양보 */
public class Test_2_3_4 {
    public static void main(String[] args) {
        Intermediator_1 i = new Intermediator_1();
        Producer_1 p = new Producer_1(i);
        Consumer_2 c = new Consumer_2(i);
        p.start();
        c.start();
    }
}
 

§28  그 대신 소비자 클래스 Consumer_2 의 run() 메쏘드 몸체에 yield() 메쏘드 불러내기 표현식을포함시켰습니다. 굵은활자 부분을 눈여겨보십시오. 이는 yield() 메쏘드 사용의 전형적인 예이므로 깊이 기억하십시오. 
 /* 소비자 클래스 - 양보 yield */
public class Consumer_2 extends Thread {
    private Intermediator_1 inter;
    public Consumer_2(Intermediator_1 inter) {
        this.inter = inter;
    }

    public void run() {
        int value = 0;
        for (int i = 0; i < 10; i++) {
            value = inter.out();
            System.out.println("Consumer got: " + value);
           yield();
        }
    }
}
 

§29  이제 이것을 실행하면 비교적 질서있고 공정한 결과를 보여줍니다. 소비자를 약간 양보하도록 만든 것입니다.
  
  c:\javafactory>java Test_2_3_4
Consumer got: 0
Producer put: 0
Producer put: 1
Producer put: 2
Consumer got: 2
Producer put: 3
Consumer got: 3
Producer put: 4
Producer put: 5
Consumer got: 5
Producer put: 6
Consumer got: 6
Producer put: 7
Consumer got: 7
Producer put: 8
Consumer got: 8
Producer put: 9
Consumer got: 9
Consumer got: 9
Consumer got: 9
c:\javafactory>
 

§30  ◆ sleep(millis) 메쏘드 사용
sleep(millis) 는 현재 실행중인 쓰레드를 인자로 주어진 millis (밀리초) 동안 잠자게 하여 그 실행권을 지정된 시간동안 다른 쓰레드에게 양보하게 하는 것입니다. 
yield()메쏘드와 다른 것은 일시 중단할 시간을 지정할 수 있다는 점과 InterruptedException 예외를 던진다는 점입니다.이 예외는 현제 쓰레드가 이미 다른 쓰레드에 의해 가로채기 당했을Interrupted 때 던쟈집니다. 
주프로그램은앞의 것과 완전히 동일하고, 다만 소비자 클래스 Consumer_3 의 쓰레드 실행 부분에 yield() 메쏘드 대신sleep() 메쏘드를 사용했다는 점만 다릅니다.  굵은활자 부분을 눈여겨보십시오. 이는 sleep() 메쏘드를 사용하는전형적인 예이므로 깊이 기억하십시오. 특히 예외포착문(try-catch 문)에 주목하십시오.


§31  /* 소비자 클래스 - 잠자기 sleep */
public class Consumer_3 extends Thread {
    private Intermediator_1 inter;
    public Consumer_2(Intermediator_1 inter) {
        this.inter = inter;
    }

    public void run() {
        int value = 0;
        for (int i = 0; i < 10; i++) {
            value = inter.out();
            System.out.println("Consumer got: " + value);
           try {
             sleep(0,1);
          } catch (InterruptedException e) { }
        }
    }
}
 
 이제 이것을 실행하면 거의 완벽한 순서를 유지하는 매우 공정한 결과를 보여줍니다. 그러나 sleep(0,1) 는1나노초(1/1000000초) 동안 잠자게 하는 것으로 이 잠자는 시간에 매우 민감한 결과가 나옵니다. 시간 선택에 매우조심해야 합니다. 


§32   
  c:\javafactory>java Test_2_3_5
Consumer got: 0
Consumer got: 0
Producer put: 0
Producer put: 1
Consumer got: 1
Producer put: 2
Consumer got: 2
Producer put: 3
Consumer got: 3
Producer put: 4
Consumer got: 4
Producer put: 5
Consumer got: 5
Producer put: 6
Consumer got: 6
Producer put: 7
Producer put: 8
Consumer got: 8
Producer put: 9
Consumer got: 9
c:\javafactory>

====================================================================================================================

--------------------------------------------------------------------------------

쓰레드 동기화
--------------------------------------------------------------------------------
 
§7   쓰레드 동기화
 ■ 쓰레드 동기화란?
우리는 지금까지 생산자/소비자 시나리오를 통해 다중쓰레드의 특성들을 공부하였습니다. 쓰레드 우선순위나 yield() 혹은sleep() 메쏘드 등을 이용해 쓰레드들이 자원을 공유하는 여러 가지 기교를 알아보았지만, 여기서는 둘 이상의 쓰레드가 서로완벽하게 질서를 지켜가며 자원을 공유하는 방법을 알아봅니다.
가령 앞의 생산자/소비자 시나리오 문제에서 생산자가 0을제공하면 소비자가 즉시 그 0을 받아가고, 생산자가 1을 제공하면 소비자는 즉시 그 1을 받아가는 식으로 공급과 소비가 정확히시간을 맞추어가며 실시되도록 하자는 것입니다. 
이렇게 하기 위해서는 생산자 쓰레드와 소비자 쓰레드가 서로 정확히 시간을 맞추어 놓고 이 시간에 따라 차례대로 번갈아 가며 실행해야 합니다. 


§8  이와 같이 둘 혹은 그 이상의 쓰레드들이 시간 진행을 서로 일치시켜 자원을 공유하는 것을 쓰레드 동기화Synchronizing Threads라 합니다.
  둘 이상의 쓰레드가 하나의 공통 자원을 시간적으로 맞추어가며 사용하도록 하는 기술을 쓰레드 동기화(Synchronizing Threads)라 한다.

여기서는 쓰레드 동기화를 위해 어떤 기술이 필요한지, 그래서 어떻게 하면 생산과 소비가  정확히 시간을 맞추어 이루어질 수 있도록 하는지 알아보겠습니다.
 

§9  ■ 쓰레드 동기화에 사용되는 기술
여기서는 쓰레드 동기화에 사용되는 몇몇 기술과 개념에 대해 알아보겠습니다. 
◆ 개체에 락 걸기
어떤 프로그램 안에서 서로 다른 동시 실행 쓰레드들이 동일한 개체에 접근할 때, 그 개체의 클래스 코드 중 이 쓰레드들이 접근하는코드 부분을 민감한 부분critical section이라 부릅니다. 보통 이 민감한 부분은 블록이나 메쏘드 부분인데, 이 부분에synchronized 란 키워드를 사용해, 그 개체가 동기화 대상임을 지정합니다. 
이와 같이 클래스의 민감한 부분에synchronized 란 키워드로 동기화 대상임을 지정하는 것을 그 클래스 개체에 대해 "락을 건다lock"고 말합니다. 즉,거기에 아무 쓰레드나 접근하지 못하도록 보호장치를 하는 것입니다. 


§10  ◆ 모니터 소유
락이 걸려있다는 것은 누군가 거기에 들어가기 위해서는 열쇠가 필요함을 의미합니다.즉, 어떤 쓰레드가 락이 걸린(synchronized 코드를 포함하는) 개체의 synchronized 코드 부분을 실행하기위해서는 거기에 접근할 수 있는 권한이 필요합니다. 이러한 용도로 사용되는 열쇠를 모니터monitor라 합니다.
당연한 일이지만, 여러 개의 열쇠가 있다면 여러 쓰레드가 동시에 접근할 수 있어 동기화의 목적을 달성할 수 없을 것입니다. 따라서 한번에 하나의 쓰레드만이 락이 걸린 개체의 모니터를 소유할 수 있습니다.
모니터는 추상적인 개념으로 구체적으로 규정된 것은 없습니다.
쓰레드가 현 개체의 모니터 소유자가 되는 방법은 다음 3가지 중 하나입니다.
그 개체의 synchronized 인스턴스 메쏘드를 실행한다.
그 개체에 대해 동기화 된 synchronized 문장의 몸체를 실행한다.
Class형 개체의 경우, 그 클래스의 synchronized static 메쏘드를 실행한다.
이 중에서 가장 흔한 경우는 첫번째 것이고 다른 것은 그리 흔치 않습니다.


§11  ◆ wait() 및 notify() 메쏘드를 사용한 락과 모니터 제어
이제 어떻게 락과 모니터를 제어하여 쓰레드 동기화를 실행할지 알아볼 차례입니다. 여기에는 Object 클래스의 wait() 및 notify() 메쏘드가 사용됩니다. 
우선 notify() 와 wait() 메쏘드가 어떤 기능을 하는지 알아보겠습니다. 
notify() 메쏘드
public final void notify() 
현재 모니터를 소유하고 실행중인 쓰레드가 자신의 작업을 마쳤음을 통보하여, (wait() 메쏘드를 실행하여) 현 개체의 모니터를기다리고 있는 쓰레드 하나를 깨웁니다. 대기 중인 쓰레드가 여러 개 있으면, 이들 중 하나를 무작위로 선택해 깨웁니다. 

§12  깨어난 쓰레드라도 현재 실행중인 쓰레드가 현 개체에 채워진 락(혹은 모니터)을 양도하기 전까지는 실행을 진행할 수없습니다. 즉, 현재 실행중인 쓰레드가 그 실행을 마치고 wait() 메쏘드를 불러내어 대기상태에 들어가야 합니다. 
깨어난 쓰레드라도 모니터를 소유한다는 보장은 없고, 현 개체에 대해 동기화 된 활성상태의 다른 쓰레드들과 실행권을 다툴 수 있습니다.
public final void notifyAll() 
현 개체의 모니터를 기다리고 있는 쓰레드들을 모두 한꺼번에 깨운다는 점을 제외하면 notify() 와 동일합니다. 
 

§13  wait() 메쏘드
public final void wait() throws InterruptedException 
현재 실행중인 쓰레드는 이 wait() 메쏘드를 불러내어 다음 실행을 위한 모니터를 기다리며 휴면상태로 들어갑니다. 
이 휴면상태는 다음 3가지 조건 중 하나가 발생할 때까지 계속됩니다.
다른 어떤 쓰레드가 현 개체에 대해 notify() 메쏘드를 불러낼 때, 우연히 깰 대상 쓰레드로 선택된다.
다른 어떤 쓰레드가 현 개체에 대해 notifyAll() 메쏘드를 불러낸다.
다른 어떤 쓰레드가 이 대기중인 쓰레드를 가로채기 한다. 
* 이 때는 InterruptedException 예외가 던져집니다.
이렇게 하여 깨게 되면  현개체 모니터의 대기자 명단에서 제외되고 쓰레드 스케줄링에 따라 다시 실행가능 상태가 됩니다. 그 다음다른 일반적 방법에 따라 쓰레드들과 그 개체에 동기화 되는 모든 권한을 다툽니다. 그리고 일단 현 개체에 대한 제어권을 얻으면,그 개체에 대한 모든 동기화 권한이 wait() 메쏘드들 불러낼 때와 똑 같은 상태로 회복됩니다. 


§14  public final void wait(long timeout)
                throws InterruptedException
현재 실행중인 쓰레드가 이 메쏘드를 불러내면, 다른 쓰레드가 현 개체에 대해 notify() 나 notifyAll() 메쏘드를불러내어 깨기 대상으로 지정되거나, 그렇지 않으면 지정한 시간 timeout 이 경과할 때까지 다음 실행을 위한 모니터를기다리며 휴면상태로 들어갑니다.
public final void wait(long timeout, int nanos)
                throws InterruptedException
대기 시간을 나노초까지 정확히 지정할 수 있다는 점을 제외하면 wait(long timeout) 와 기능이 같습니다. 


§15  동기화 과정
여기서 wait() 및 notify() 메쏘드가 어떤 과정을 통해 동기화를 실행하는지그 과정을 알아보겠습니다. 이를 아래 <그림2.4.1> 처럼, 하나의 자원 개체와 이를 공유하고자 하는 3개의 쓰레드A, B, C를 가정합니다. 
1. 현재 3개의 쓰레드 A, B, C 중 쓰레드 A가 대상 자원 개체에 접근권, 즉,모니터를 소유하고 실행중에 있습니다. 이 말은 쓰레드 A가 락걸린 개체의 synchronized 메쏘드를 이행중이라는 것입니다.그리고 나머지 쓰레드 B와 C는  wait() 메쏘드를 불러내어 모니터를 기다리면서 휴면 중에 있습니다. 
2. 이제쓰레드 A가 notify() 메쏘드를 불러내어 실행을 끝냈음을 다른 쓰레드에게 통보합니다. 그러면 모니터를 기다리는 두 쓰레드B와 C 중 어느 하나가 임의로 선택되어 깨어납니다. 여기서는 쓰레드 B가 우연히 깨어났다고 가정합시다.
3. 다시 쓰레드 A는 wait() 메쏘드를 불러내어 다음 실행을 위해 모니터 기다리는 대기자 상태로 휴면에 들어갑니다. 그와 함께 깨어난 쓰레드 B는 모니터를 소유하고 실행에 들어갑니다.
이 순환과정은 모든 쓰레드가 죽을 때까지 (실행을 마칠 때까지) 반복됩니다. 

 

wait() 및 notify() 메쏘드 사용을 통한 동기화 과정
쓰레드A
실행중
모니터소유
동기화메쏘드

실행
락걸린 개체
synchronized method()
 
쓰레드B
대기중
(휴면)
쓰레드C
대기중
(휴면)
notify()
깨움

깨기 대상으로 선정

쓰레드A
실행중
모니터소유

락걸린 개체
synchronized method()

쓰레드B
대기중
(깨어남)
쓰레드C
대기중
(휴면)
wait()
대기요청
모니터양도

모니터 소유자로 선정

쓰레드A
대기중
(휴면)

락걸린 개체
synchronized method()
동기화메쏘드

실행
쓰레드B
실행중
모니터소유
쓰레드C
대기중
(휴면)

 

§18  /* 중개자 클래스 - 쓰레드 동기화 */
public class Intermediator {
   private int contents;
   private boolean light_on = true;
   public synchronized void in(int value) {
      // 소비자가 소비를 마치고 (done == true) 통보할 때까지 기다림 
      while (light_on == false) {
         try {
           wait(); 
         } catch (InterruptedException e) {
         }
      }
      // 소비자가 소비를 마쳤으면 (done == true) 생산 시작 
      contents = value;
      light_on = false;
      notify(); // light_on=false 값과 함께 생산 마쳤음을 통보 
   } 

   public synchronized int out() {
      // 생산자가 생산을 마치고 (done == false) 통보할 때까지 기다림 
      while (light_on == true) {
         try {
           wait(); 
         } catch (InterruptedException e) { }
      }
      // 생산자가 생산을 마쳤으면 (done == false) 소비 시작 
      light_on = true;
      notify();// light_on=true 값과 함께 소비 마쳤음을 통보 
      return contents;
    }
}
 


§18  /* 소비자 클래스 - 쓰레드 동기화 */
public class Consumer extends Thread {
    private Intermediator inter;
    public Consumer(Intermediator inter) {
        this.inter = inter;
    }

    public void run() {
        int value = 0;
        for (int i = 0; i < 10; i++) {
            value = inter.out();
            System.out.println("Consumer got: " + value);
        }
    }
}
 


§20  /* 생산자 클래스 - 쓰레드 동기화 */
public class Producer extends Thread {
    private Intermediator inter;
    public Producer(Intermediator inter) {
        this.inter = inter;
    }

    public void run() { 
        for (int i = 0; i < 10; i++) {
            inter.in(i);
            System.out.println("Producer put: " + i);
        }
    }
}
 
 /* 생산자/소비자 시험 프로그램 - 쓰레드 동기화 */
public class Test_2_3_6 {
    public static void main(String[] args) {
        Intermediator i = new Intermediator();
        Producer p = new Producer(i);
        Consumer c = new Consumer(i);
        p.start();
        c.start();

 

 

출처 : http://www.welog.net/gbbs/bbs/board.php?bo_table=java&wr_id=127

+ Recent posts