2014년 5월 12일 월요일

Java Thread

1. Thread 란?

 - 프로그램의 실행 흐름, 프로그램을 구성하고 있는 실행 단위
 - 흔히 하나의 흐름으로 구성되어 있는 프로그램을 Single Thread, 여러 작업을 동시에 수행 가능하게 하는 것이 Multi-Thread 다.

2. Multi-Thread 를 사용해야 하는 이유

  단일 스레드 애플리케이션의 문제는 다른 것들이 시작하기 전에 작업을 완료해야 한다는 것에 있다. 멀티스레드는 동시성을 시뮬레이션하는 단일 프로세서 시스템에서 성능을 증가 시킬수 있다. 하나의 쓰레드가 진행할 수 없을 경우 , 다른 것이 프로세서를 사용할 수 있다.

3. Thread 의 라이프 싸이클
 

  새 스레드는 new 상태에서 라이프 사이클을 시작한다. 그것은 스레드를 runnable 상태에
놓으며, 프로그램이 스레드를 시작할 때까지 이 상태로 남아 있다. runnable 상태에서 스레드는 자체 태스크 수행하는 것으로 간주된다.

  때로 runnable 스레드는 다른 스레드가 태스크를 수행하길 기다리는 동안 waiting 상태로 전환한다. waiting 스레드는 기다리는 스레드에 통보할 때만 runnable 상태로 다시 전환한다.
  
  runnable 스레드는 지정된 대기 시간 동안 timed waiting 상태로 들어갈 수 있다.
지정된 대기 시간이 만료하거나 기다리던 이벤트가 발생하면 runnable 상태로 다시 전환한다.

  timed waiting과 waiting 스레드는 프로세서를 사용할 수 없다. 선택적 간격이 제공되는 경우
runnable 스레드는 timed waiting 상태로 전화할 수 있다. 이러한 스레드는 다른 스레드에 의해 통보될 때 혹은 초과 간격이 만료할 때 runnable 상태로 다시 돌아간다.

 스레드를 timed waiting 상태에 놓는 또 다른 방법인 sleep 은 runnable 상태로 돌아간 후에 지정된 시간 동안 timed waiting 상태에 머문다. 스레드는 수행해야 할 작업이 없을 때는 수면 상태가 된다.

 즉시 완료될 수 없는 태스크 수행을 시도하고 , 태스크가 완료될 때까지 반드시 기다려야 할 때 runnable 스레드는 blocked 상태로 전환한다. 예를 들어, 스레드가 입력/출력 요청을 할 때 운영체제는 I/O 요청이 완료될 때까지 스레드가 실행되는 것을 차단하며, 완료 시점에서 blocked 스레드가 runnable 상태로 전환함으로써 실행을 재개한다. blocked 스레드는 프로세서를 사용할 수 없다.

 runnable 스레드는 성공적으로 태스크를 완료할 때 terminated 상태 즉, 죽는다. 또는 종료된다. 

4. 스레드 우선 순위 및 스레드 스케쥴링

  모든 자바 스레드는 운영체제가 스레드가 예약되는 순서를 결정하는데 도움을 주는 
 스레드 우선 순위를 가지고 있다. 

 자바 우선 순위는 MIN_PRIORITY(상수1)~MAX_PRIORITY(상수 10) 사이의 범위다. 

 Default 값으로 NORM_PRIORITY(상수5)가 주어진다.

 ※ 스레드 스케쥴링은 플랫폼 의존적이다. 다중 스레드 프로그램의 동작은 자바 구현에
     따라 다를 수 있다.

 5. Runnables 및 Thread  클래스
  
   자바에서는 Runnable 인터페이스로 구현하거나 Thread 클래스를 상속받아서
  스레드 구현이 가능하다. 그러나 멀티 스레드에서 선호하는 방식은 Runnable 인터페이스다

  Runnable 객체는 다른 태스크와 함께 동시에 실행할 수 있는 "태스크" 를 나타낸다.
  Runnable 인터페이스는 단일 메서드 run 을 선언하여 Runnable 객체가 수행해야 하는
  태스크를 정의하는 코드를 포함한다. Runnable을 실행하는 스레드가 생성되고 시작될 때
  스레드는 새 스레드에서 실행되는 Runnable 객체의 run 메서드를 호출한다.


 Thread 구현 예제


1. PrintTask.Java  

import java.util.Random;

/**
 * Thread 로 선언하는것보다 Runnable 로 하는것이 더 좋으며
 * 멀티 쓰레드 관리를 편하게 하기 위한 테스트 
 * @author blessldk
 *
 */
public class PrintTask implements Runnable{

private final int sleepTime;
private final String taskName;
private final static Random generator= new Random();

/**
* 각각의 task 이름을 받아와서 설정해볼까? 그렇게 하자
*/
public PrintTask(String name){
this.taskName = name;
sleepTime = generator.nextInt(5000);
}
@Override
public void run() {
// TODO Auto-generated method stub
try{
while(true){
System.out.printf("%s going to for %d milli\n" , taskName , sleepTime);

Thread.sleep(sleepTime);
}
}
catch(Exception e){
e.printStackTrace();
}
System.out.printf("%s done sleeping\n" , taskName);
}

}

2. ThreadCreator.Java


public class ThreadCreator {

public static void main(String[] args) {
System.out.println("Create Thread");
Thread thread1 = new Thread(new PrintTask("task1"));
Thread thread2 = new Thread(new PrintTask("task2"));
Thread thread3 = new Thread(new PrintTask("task3"));
System.out.println("Created Thread Print Task");
thread1.start();
thread2.start();
thread3.start();
System.out.println("task started. main 끝");
}
}


Executor 프레임워크로 스레드 관리

위와 같이 명확하게 스레드를 생성하는 것이 가능하지만, Executor 인터페이스를 사용하여 Runnable 객체의 실행을 관리하는 것을 권장한다.
Executor 객체는 Runnable을 실행하기 위해 스레드 풀(Thread pool)이라고 하는 스레드 그룹을 생성하고 관리한다. Executor를 사용하는 것은 스스로 스레드를 생성하는 것에 비해 장점을 가진다. Executors는 새로운 스레드 생성의 오버헤드를 제거하기 위해 기존의 스레드를 다시 사용할 수 있고 어플리케이션이 리소스를 과도하게 소모하는 너무 많은 스레드를 생성하지 않고, 스레드의 수를 최적화하여 성능을 향상시킬 수 있다.

Executor 인터페이스는 Runnable을 인수로서 받는 execute라는 단일 메서드를 선언한다.
Executor 는 execute 메서드에 전달된 모든 Runnable을 스레드 풀에 있는 스레드 중 하나로 할당한다. 사용 가능한 스레드가 없는 경우, Executor는 새 스레드를 생성하거나 스레드가 사용 가능해지길 기다린다. 

ExecutorService 인터페이스는 Executor를 확장하고 Executor는 execute 메서드에 전달된 모든 Runnable을 스레드 풀에 있는 스레드 중 하나로 할당한다. 사용 가능한 스레드가 없는 경우, Executor는 새 스레드를 생성하거나 스레드가 사용 가능해지길 기다린다.

3. TaskExector.java

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class TaskExecutor {
public static void main(String[] args) {
PrintTask task1 = new PrintTask("task1");
PrintTask task2 = new PrintTask("task2");
PrintTask task3 = new PrintTask("task3");
System.out.println("Starting Executor");
ExecutorService threadExecutor = Executors.newCachedThreadPool();
threadExecutor.execute(task1);
threadExecutor.execute(task2);
threadExecutor.execute(task3);
threadExecutor.shutdown();
System.out.println("Tasks started, main ends");
}
}

댓글 2개:

  1. threadExecutor.shutdown()을 호출하지 않는 경우, 문제가 있습니까?
    기존 스레드가 다시 사용 가능해질 때까지 기다리는 경우, 무한 대기할 수도 있습니까??

    답글삭제
    답글
    1. ShutDown 은 현재까지 이미 추가된 작업은 돌리되 더이상의 작업은 받지 않겠다 라는 의미입니다.
      완전 중지 시키는 메소드로는 ShutDownNow() 가 있습니다.
      이는 현재 실행중인 모든 작업조차 중지시키고 반환시키는 메소드입니다.

      삭제