Thread pool

Development/Java 2012. 3. 16. 10:56

jdk1.5.x 버전부터 
java.util.concurrent 패키지를 제공하고 있다.
패키지 하위에 유용한 클래스들이 많이 존재하므로 
계속해서 살펴보고 익혀야 겠다.

Java5에서  Thread pool을 사용하는 방법이다.
이전 jdk버전에서는 Thread pool을 직접구현해야 했고,
구현을 한다해도, 검증되지 않은 성능 Issue는 항상 개발자를 머리아프게 했었는데...

정말 Good이군~~
    private int threadCount=5;
    private ExecutorService executorService;


    /** thread 숫자를 설정한다. 초기치는 5개 */
    public void setThreadCount(int threadCount) {
        this.threadCount = threadCount;
    }

         - Thread Pool생성

        if (executorService==null) {
            logger.debug("Creating new thread pool for message delivery");
            executorService = Executors.newFixedThreadPool(threadCount);
        }

        - Thread 실행

        /* Spawn a task into the thread pool. */
        executorService.execute(new Runnable(){
            public void run() {
                   //some code that takes some time to run.
            }
        });


       정말 간단하다.
       물론 실제 로직에 적용하기 위해선 부가적으로 추가해야 할것들이 있지만
       우선 jdk기본 패키지에 포함되었다는것은 성능검증은 완료된것이라 봐야 하므로
       의심없이 사용할 수 있을듯 하다.(물론 테스트는 해봐야 한다.)

      만든이가 너무 고맙다!
      

출처 -  http://jace.tistory.com/70 







쓰레드란, 자바에서 유일하게 제공되는 병렬처리용 컨트롤 방식이다. (자바는 프로세스를

fork 할 수 없음)

병렬처리를 위해서 쓰레드를 생성하지만, 쓰레드가 반복적으로 생성, 소멸 과정을 거친다면

단순 쓰레드를 생성하는 방식으로는 리소스를 많이 사용하기 때문에 비 효율적이다. 이때

고려될 방법이 미리 쓰레드를 생성해 놓고, 메모리에서 호출하는 방식인 쓰레드 풀 기법을

사용하면 보다 효율적으로 쓰레드를 사용할 수 있다.

쓰레드풀은 갠적으로 구현해서 사용해도 좋으나, 자바에서 기본적으로 제공하는 검증 된 

쓰레드 풀을 사용하는 것을 추천한다.

 

1. Executor 생성(10개의 풀)

final ExecutorService taskExecutor = Executors.newFixedThreadPool(10);

 

//=====>반복시작

2. 쓰레드로 만들고자 하는 클래스 오브젝트 생성

Runnable myRunnable= new Runnable(){

 public void run(){

MyThread myThread = new MyThread();

}

};

 

3. 쓰레드 실행

taskExecutor.execute(myRunnable);

//=====>반복종료

 

 

4.쓰레드로 생성되는 클래스는 Runnable Interface 를 Implements 하는 방식으로 구현 한다.

public class MyThread implements Runnable {

  public MyThread()

  {

  }

 

  public void run()

  {

  }

}

 

출처 -  http://blog.naver.com/rainbowhand?Redirect=Log&logNo=13012901119 







자바 1.5 버전 부터 쓰래드를 지원하기 위한 API가 강력해진거 같다.

 

모, 일단 라이브러리가 많이 늘어난거 같다. 래치, 배리어, 쓰레드풀 , 퓨처패턴 등등등......

 

나중에 필요할때 써 먹을라고, 관련되는 녀석 공부하던중에

 

자바 1.5부터 지원되는 쓰레드풀 생성하는 아주작은 샘플을 만들어 보았습니당~

 

 

/**
 * 
 */
package test;

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

/**
 * @author 이준성
 *
 */
public class ThreadPoolTest {

 final static int THREADCOUNT = 10;
 
 /**
  * @param args
  */
 public static void main(String[] args) {
  
  
  // 쓰레드 풀 생성. THREADCOUNT 개수 만큼
  ExecutorService exec = Executors.newFixedThreadPool(THREADCOUNT);
  
  for(int i = 0 ; i<THREADCOUNT*2 ; i++) {
   
   exec.execute(new TestThread());
  }
  
  // 쓰레드풀 생성하고 전부다 사용후에는 반드시 셧다운 해야함~
  exec.shutdown();
  

 }
 
 
 
 // 테스트 쓰래드
 public static class TestThread implements Runnable{

  public void run() {
   
   for(int i=1 ; i<= 100 ; i++) {
    
    System.out.println(Thread.currentThread().getName() + " => "+i);
    try {
     Thread.sleep(100);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
     
   }
   
  }  
  
 }

}

 

 

Executors.newFixedThreadPool(THREADCOUNT) 이 부분에 의해서 쓰레드 풀을 생성해서 풀을 넘겨 줍니다.

 

사실 이거말고도 Executors클래스로 생성할수 있는 쓰레드 풀이 3개가 더 존재하더군요.

캐쉬, 싱글익스큐트 등등.

 

Executors.newFixedThreadPool 이 녀석에 의해서 생성되는 쓰레드 풀은 작업이 하나하나 등록할때마다 새로운 쓰레드를 생성한다고 합니다.

 

작업을 등록하는 부분은 exec.execute(new TestThread()) 이 부분 입니다.

 

쓰레드 풀을 사용하지 않을 경우는 Thread를 상속받던가 Runnable을 구현해서 생성하고 스타트 했지만, 쓰레드풀에 작업을 등록하려면 Runnable 이녀석을 구현해서 인자로 넘겨 주기만 하면 됩니다.

 

또 모든 작업이 끝날시에는 셧다운을 해야하는데, 쓰레드 풀 특성을 곰곰히 생각해보시면, 쉽게 아실거라 생각이 듭니다.

쓰레드풀 자체에서 쓰레드 관리(?) 하기 위한 쓰레드가 생성이 되기 때문에, 셧다운을 해주셔야 합니다. 하지않으면~ 프로세스가 남아있더군요.

 

셧다운 하는 방식도 여러가지가 있더군요. 모두 끝낸후에 셧다운 및 강제종료등등

위에서 사용한 방식은 모두 끝낸후에 종료입니다. 아~ 그리고 셧다운후에 작업을 등록하면 익셉션이 발생한다고 합니다.

 

쓰레드풀에서 퓨터 패턴도 적용되어서~ 작업이 끝난 후에 결과값도 받을수 있습니다.

서브밋이라는 메소드를 사용하면 되더군요.

 

시간날때, 다른형태의 쓰레드풀 사용하는 방법과 퓨쳐 패턴 사용방법을 올리지요~


출처 -  http://blog.naver.com/lover4908?Redirect=Log&logNo=110039084541 








* Executor
 * Executor는 굉장히 단순한 인터페이스로 보이지만 아주 다양한 여러 가지 종류의 작업 실행 정책을 지원하는 
 * 유연하면서도 강력한 비동기적 작업 실행 프레임웍의 근간을 이루는 인터페이스다
 
 * Executor는 작업등록(task submission) ,작업실행(task Execution)을 분리하는 표준적인 방법이며,
 * 각 작업은 Runnable의 형태로 정의한다.
 
 * Executor인터페이스를 구현한 클래스는 작업의 라이프 사이클을 관리하는 기능도 갖고 있고,
 * 몇가지 통계값을 뽑아내거나 애플리케이션에서 작업 실행 과정을 관리하고 모니터링하기 위한 기능도 갖고 있다.
 * 
 * Executor의 구조는 프로듀서-컨슈머 패턴에 기반하고 있으며, 작업을 생성해 등록하는 클래스가 
 * 프로듀서(처리해야할 작업을 생성하는 주체)가 되고, 작업을 실제로 실행하는 스레드가 
    컨슈머(생성된 작업을 처리하는 주체)가 되는 모양을 갖추고 있다.

 * 일반적으로 프로듀서-컨슈머 패턴을 애플리케이션에 적용해 구현할 수 있는 가장 쉬는 방법이
   바로 Executor 프레임웍을 사용하는 방법이다.
  1) Executors.newFixedThreadPool(100);  //100개의 스레드
    - 처리할 작업이 등록되면 그에 따라 실제 작업할 스레드를 하나씩 생성한다.
    - 생성할 수 있는 스레드의 최대 개수는 제한되어 있다.
    -  제한된 개수까지 스레드를 생성하고 나면 더이상 생성하지 않고 스레드 수를 유지한다.
  2) Executors.newCachedThreadPool();
    - 캐시 스레드풀은 현재 풀에 갖고 있는 스레드의 수가 처리할 작업의 수보다 많아서 쉬는 스레드가 많이 발생할 때 쉬는 스레드를
        종료시켜 훨씬 유연하게 대응 할 수 있으며, 처리할 작업의 수가 많이 지면 필요한 만큼 스레드를 새로 생성한다.
    - 스레드의 수에 제한을 두지 않는다.
   3) Executors.newSingleThreadExecutor();
  - 단일 스레드로 동작하는 Executor 로서 작업을 처리하는 스레드가 단 하나 뿐이다.
  - 만약 작업중에 Exeception 이 발생해 비정상적으로 종료되면 새로운 스레드를 하나 생성해 나머지 작업을 실행한다.
  - 등록된 작업은 설정된 큐에서 지정하는 순서(FIFO,LIFO,우선순위)에 따라 반드시 순차적으로 처리된다.
   4) Executors.newScheduledThreadPool(100);
  - 일정시간 이후에 실행하거나 주기적으로 작업을 실행할 수 있으며, 스레드의 수가 고정되어 있는 형태의
     Executor.Timer 클래스의 기능과 유사하다.

ExecutorService
  - newFixedThreadPool,newCachedThreadPool 팩토리 메소드는 일반화된 형태로 구현되어 있는  ThreadPoolExecutor
       클래스의 인스턴스를 생성한다.
  - 생성된 ThreadPoolExecutor 인스턴스에 설정값을 조절해 필요한 형태를 갖추고 사용할 수도 있다.
  - Executor 를 구현한 클래스는 대부분 작업을 처리하기 위한 스레드를 생성하도록 되어 있다. 하지만 JVM 은 모든 스레드가 
     종료되기 전에는 종료하지 않고 대기하기 때문에 Executor를 제대로 종료시키지 않으면 JVM 자체가 종료되지 않고
      대기하기도 한다.
  - Executor는 작업을 비동기적으로 실행하기 때문에 앞서 실행시켰던 작업의 상태를 특정시점에 정확히 파악하기가 어렵다.
     어떤작업은 이미 완료되었을 수도 있고, 몇몇 작업은 실행중일 수 있고, 또다른 작업은 큐에서 대기하고 있을 수 있다. 
  - 애플리케이션을 종료하는 과정을 보면 안정한 종료방법과(작업을 새로 등록하지 못하고, 시작된 모든작업이 끝날때 까지 기다림) 
      강제적인 종료 방법이 있겠다. 물론 그사이에 위치하는 여러가지 종료방법이 있겠다.
  - Executor가 애플리케이션에 스레드 풀등의 서비스를 제공한다는 관정으로 생각해 보면 Executor 역시 안전한 방법이든,
      강제적인 방법이든 종료절차를 밟아야할 필요가 있다.
  - 그리고 종료절차를 밟는 동안 실행중이거나,대기중인 작업을 어떻게 처리했는지 애플리케이션에 알려줄 의무가 있다.
  - 이처럼 서비스를 실행하는 동작주기와 관련해 Ececutor를 상속받은 ExecutorService 인터페이스에는 동작주기를 관리할 수 있
    는 여러가지 메소드가 추가되어 있다.
public interface ExecutorService extends Executor{
void shutdown();                                                --> 안전한 종료
List<Runnable> shutdownNow();                         --> 강제종료
boolean isShutdown(); 
boolean isTerminated();
boolean awaitTermination(long timeout,TimeUnit unit) throw InterruptedExeception;
                                                      --> ExecutorService가 종료상태로 들어갈 때까지 기다리고자 할때 사용
..,
}

보통 shutdown() 메소드 실행 후 바로 awaitTermination()을 실행하면 Executor를 직접 종료시키는것과
비슷한 효과를 얻을 수 있다.
ExecutorService exec= Executors.newFixedThreadPool(100);
Callable<V> task = new Callable<V>() {

public V call() throws Exception {
return null;
}
};
Future<V> a = exec.submit(task);
a.get();

 




 * ThreadPoolExecutor :

 * Executors 클래스에 들어있는 newCachedThreadPool, newFixedThreadPool ,newScheduledThreadPool

 * 과 같은 팩토리메소드에서 생성해 주는 Executor에 대한 기본적인 내용이 구현되어 있는 클래스이다.



 * 팩토리메소드를 사용해 만들어진 스레드 풀의 기본 실행 정책이 요구 사항에 잘 맞지 않는다면 ThreadPoolExecutor 클래스의

 * 생성자를 직접 호출해 스레드 풀을 생성할 수 있으며, 생성자에 넘겨주는 값을 통해 스레드 풀의 설정을 마음대로 조절할 수 있다.

 

public ThreadPoolExecutor (int corePoolSize,                  // 스레드풀을 사용할때 원하는 스레드의 개수

int maximumPoolSize,           //동시에 동작할 수 있는 스레드의 최대값

long keepAliveTime,             //스레드의 유지시간

TimeUnit unit,

BlokingQueue<Runable> workQueue,

ThreadFactory threadFactory,

RehectedExecutionHandler handker)

{....}


* newFixedThreadPool의 

*    - 스레드풀의 코어크기,최대크기를 newFixedThreadPool(100) 처럼 파라미터로 지정한 값으로 설정 

*    - 시간제한은 무제한

*    - LinkedBlokingQueue 사용

* newCachedThreadPool

*    - 스레드풀의 최대 크기를 Integer.MAX_VALUE값으로 설정

*    - 코어 크기 :0

*    - 스레드 유지시간은 1분

*    - SynchronousQueue 사용 : 프로듀소에서 생긴 작업을 컨슈머인 스레드에 직접 전달

*       따지고 보면 큐가 아니며, 단지 스레드 간에 작업을 넘겨주는 기능을 담당함

*    - SynchronousQueue에 작업을 추가하려면 컨슈머인 스레드가 이미 작업을 받기위해 대기하고 있어야 하며,

*       대기중인 스레드가 없는 상태에서 스레드의 갯수가 최대 크기보다 작다면 새로운 스레드를 생성시켜 동작한다.

*       최대크기에 다다른 상태라면 작업을 거부하도록 되어 있다.

*    따라서 newCachedThreadPool에서 만들어낸 스레드풀은 크기가 무제한 늘어나며, 

     사용량이 줄어들면 스레드갯수가 적장히 줄어든다


출처 -  http://blog.naver.com/minis24?Redirect=Log&logNo=80152375114 








ExecutorService 를 이용하여 간단하게 ThreadPool를 구현할 수 있다.

 

int poolSize = 10;

//고정 개수의 ThreadPool을 생성한다.(이외 다른 종류는 API를 참고한다.)

ExecutorService e = Executors.newFixedThreadPool(poolSize);

 

//실행을 할때 해당 작업의 완료 여부나 return 값을 알기 위해서는 submit을 이용하고 그외는 execute를 이용한다.

//단순 실행일때

e.execute(Runnalbe);

 

//작업 종료 여부를 알려고 할때

Future f = e.submit(Runnable);

f.isCancelled(); // 작업 종료하기 전 취소 되었는지 여부를 알수 있다 , true - 취소

f.isDone(); //작업이 성공적으로 종료되었으면 true

 

//return 값을 알고 싶을때

Future f = e.submit(Callable);

Object o = f.get(); // 작업이 완료될때까지 기다리며 완료 될때 return 값을 받는다.


출처 -  http://cafe.naver.com/withoracle.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=1009& 









Executor 는 어떤 작업을 처리하기 위한 스래드를 생성하는 녀석이다. 이 녀석이 만약 어떤 스래드가 죽어야 하는데 죽지 않고 대기 하는 상황이 생긴다면 JVM 도 죽지 않는다.

즉, 프로그램이 생성되고 메모리 상에서 사라지지 않고 프로그램 생성 시 마다 쌓인다는 것을 의미한다.

Executor 클래스에 내부적인 메소드는 프로그램을 실행하는 메소드만 존재 하고 관련된 스래드를 죽이는 메소드는 존재 하지 않는다고.. 책이 그랬다. ... 그래서 난 의심이 많기 때문에.. 직접 따라가 봤다.

package java.util.concurrent;

public interface Executor
{
    public abstract void execute(Runnable runnable);
}

음... 진짜 그랬다.. ㅡㅡ;

워.. 그럼 Executor 은 실행만 하고... 기타 운영되는 시점에서 자체적으로 운영 최적화 말고는 정말 없는 것인가? 그럼 보통 스래드와 별반 다를게 없다는 것 아닌가..
항상 그러지만 똑똑한 사람들은 먼저 태어났다. 이미 이러한 내용들은 구현이 돼 있다.

바로 ExecutorServiece 란 넘이 이 역할을 하고 있다.
ExecutorServiece 는 Executor을 상속 받아 기본 실행하는 기능을 유지 하면서 안전하게 종료 할 수 있는 기능들을 제공한다.

public interface ExecutorService extends Executor
{
    public abstract void shutdown();

    public abstract List shutdownNow();

    public abstract boolean isShutdown();

    public abstract boolean isTerminated();

    public abstract boolean awaitTermination(long l, TimeUnit timeunit)
        throws InterruptedException;

    public abstract Future submit(Callable callable);

    public abstract Future submit(Runnable runnable, Object obj);

    public abstract Future submit(Runnable runnable);

    public abstract List invokeAll(Collection collection)
        throws InterruptedException;

    public abstract List invokeAll(Collection collection, long l, TimeUnit timeunit)
        throws InterruptedException;

    public abstract Object invokeAny(Collection collection)
        throws InterruptedException, ExecutionException;

    public abstract Object invokeAny(Collection collection, long l, TimeUnit timeunit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

역시 의심이 많은 나는... 역시 따라가봤다.
종료 하는 메소드들이 있다. 거기다가 현재 스래드가 종료 되었는지를 확인하는 메소드도 있었다.

다음은 책에서 손가락으로 복사한 내용이다. 아휴... 힘들어... ㅡㅡ;

내부적으로 ExecutorService 가 갖고 있는 동작 주기에는 running, shutting dowun, terminated의 세가지 상태가 있다. ExecutorService 를 처음 생성 했을때에는 실행 중 상태로 동작한다. 어느 시점엔가 shutdown 메소드를 실행하면 안전한 종료 절차를 진행하며 종료중 상태로 들어간다 . 이 상태에서는 새로운 작업을 등록받지 않으며, 이전 등록되어 있던 작업 까지는 모두 끝마칠 수 있다. shutdownNow 메소드를 실행하면 강제 종료 절차를 진행한다. 현재 진행 중인 작업도 가능한 한 취소시키고, 실행되지 않고 대기 중이던 작업은 더 이상 실행시키지 않는다.
ExecutorService 의 하위 클래스인 ThreadPoolExecutor는 이미 종료 절차가 시작되거나 종료된 이후에 새로운 작업을 등록하려 하면 실행 거절 핸들러를 통해 오류로 처리한다. 실행 거절 핸들러에 따라 다르지만 등록하려 했던 작업을 조용히 무시할 수도 있고, RejectedExecutionException을 발생시켜 오류로 처리 하도록 할 수도 있다. 졸요 절차가 시작된 이후 실행 중이거나 대기 중이던 작업을 모두 끝내고 나면 ExecutorService는 종료 상태로 들어간다. ExecutorService가 종료 상태로 들어갈 때까지 기다리고자 한다면 awaitTermination메소드로 대기할 수도 있고, isTerminated 메소드를 주기적으로 호출해 종료 상태로 들어갔는지 확인할 수도 있고, isTerminated 메소드를 주기적으로 호출해 종료 상태로 들어갔는지 확인할 수도 있다. 일반적으로 shutdown 메소드를 실행한 이후 바로 awaitTermination을 실행하면 마치 ExecutorService를 직접 종료 시키는 것과 비슷한 효과를 얻을 수 있다.

출처 -  http://suein1209.tistory.com/333 

타스크를 실행하기 위해 쓰레드를 생성하는데
만일 실행중인 쓰레드를 중단하고 싶을 때 ExecutorService 를 사용한다.

ExecutorService 는 다음과 같이 세가지 상태가 있다.

- running
- shutting down
- terminated

그리고 다음과 같은 메서드가 존재한다.

shutdown();
shutdownNow();
submit();

shutdown() 은 지금 실행중인 것은 실행할 수 있도록 하지만 
더 이상 새로운 타스크를 받아들이지 않는다.
shutdownNow() 는 지금 실행중인 것을 바로 끝낸다.
리턴 값으로 시작하지 않은 타스크를 돌려주지만
시작했지만 끝내지 않은 타스크의 정보는 알 수 없다.
이것은 프로그램으로 해결할 수 있다.

한가지 주의 할 점은 실제로 작업이 끝났는데도
끝났다고 기록하기 전에 shutdownNow 이 불려지면
타스크가 완료되지 않았다고 할 수 도 있다.

그러나 여러번 실행되어도 상관없는 대부분의 어플리케이션은
이것을 신경 쓸 필요가 없을 것이다.

submit() 는 인수로 넘겨주는 Runnable 이나 Callable 이
실제로 실행되는 쓰레드로 안전하게 넘겨주고
리턴되는 Future 의 get() 이 결과값을 정상적으로
받을 수 있도록 해 준다.


출처 -  http://joejeon.tistory.com/388 

















'Development > Java' 카테고리의 다른 글

인터페이스(interface)  (0) 2012.03.18
DAO, DTO, VO  (2) 2012.03.16
java 어노테이션(Annotation)  (0) 2012.03.14
JVM 인코딩 설정(Dfile.encoding 옵션)  (0) 2012.03.13
자바빈즈(javabeans)  (0) 2012.03.11
Posted by linuxism
,