등록일:2018-09-10 20:41:36 작성자: 제목:자바 간단한 쓰레드풀 구현(Java Simple ThreadPool)
ThreadPool(이하 "쓰레드풀")의 개념은 학부생 시절에도 배웁니다. 하지만 개념으로만 파악하고 있기엔 뜬구름 잡는 느낌이 없지 않아 있어서 이번 포스팅은 실제로 ThreadPool을 구현해보려고 합니다. 정말 간단한 ThreadPool을 구현하는 것이니 참고만 해주셨으면 합니다. :3
이 클래스는 Task를 받아서 삽입하고 꺼내주는 용도의 큐로써 작성했습니다. 큐에 Task가 없으면 .wait() 메소드를 통해 잠시 멈춰 있고 반대로 큐에 Task가 가득 차 있으면 .notifyAll() 메소드를 호출합니다.
3.2. ThreadPoolRunnable
이번엔 쓰레드풀에서 실제 Task 처리를 위한 쓰레드를 생성하는 클래스입니다.
publicclassThreadPoolRunnableimplementsRunnable{privateint id;// Runnable IDprivateThreadPoolQueue queue;privatevolatileboolean running =true;privateboolean DEBUG =false;// Runnable 초기화publicThreadPoolRunnable(int THREAD_ID,ThreadPoolQueue queue){this.id = THREAD_ID;this.queue = queue;
console("ThreadPoolRunnable["+id+"] is created.");}// Runnable 실행@Overridepublicvoid run(){while( running ){try{Thread.sleep(10);
console("ThreadPoolRunnable["+id+"] is working.");Runnable r =(Runnable) queue.dequeue();
r.run();}catch(InterruptedException e ){
stop();// 인터럽트 예외 발생시 해당 Runnable 정지}}
console("ThreadPoolRunnable["+id+"] is dead.");}// Runnable 정지publicvoid stop(){
running =false;
console("ThreadPoolRunnable["+id+"] is stopped.");}// 현재 Runnable 디버깅 설정publicvoid toggleDebug(boolean flag){this.DEBUG = flag;}publicvoid console(String msg){if( DEBUG )System.out.println(msg);}}
단순하게 생각한다면 ThreadPoolQueue에서 Task를 꺼내고 그것을 처리하는 것이 전부입니다.
TIPvolatile vs syncronized syncronized 키워드는 블럭으로 이루어진 코드의 동작(operation)에 대한 원자성 보장이라면 volatile는 선언된 변수의 원자성을 보장합니다. 자바 버전 1.5 이상이어야 제대로 동작합니다.
3.3. ThreadPool
위의 두가지 클래스를 멤버 변수로 하는 쓰레드풀 객체입니다.
import java.util.ArrayList;import java.util.List;/* 쓰레드풀 클래스 */publicclassThreadPool{privateThreadPoolQueue queue;privateList<ThreadPoolRunnable> runnableList =newArrayList<ThreadPoolRunnable>();privatevolatileboolean running =true;/* 쓰레드풀 초기화 */publicThreadPool(int MAX_THREAD_NUM,int MAX_QUEUE_SIZE){
queue =newThreadPoolQueue(MAX_QUEUE_SIZE);for(int i =0; i < MAX_THREAD_NUM; i++){
runnableList.add(newThreadPoolRunnable(i, queue));}for(ThreadPoolRunnable r : runnableList ){newThread(r).start();}}/* 쓰레드풀 실행 */publicSynchronizedvoid execute(Runnable item)throwsException{if(!running ){thrownewException("Thread Pool is not running.");}
queue.enqueue(item);}/* 쓰레드풀 정지 */publicSynchronizedvoid stop()throwsInterruptedException{
running =false;for(ThreadPoolRunnable r : runnableList ){
r.stop();}}/* Runnable console 디버깅 플래그 설정 */publicvoid toggleDebugWithRunnable(boolean flag){for(ThreadPoolRunnable r : runnableList ){
r.toggleDebug(flag);}}/* Queue console 디버깅 플래그 설정 */publicvoid toggleDebugWithQueue(boolean flag){
queue.toggleDebug(flag);}}
4. Test
실제로 위에서 만든 쓰레드풀을 활용한 테스트 프로그램을 작성해봤습니다.
publicclassTestMain{publicstaticvoid main(String[] args){// 쓰레드가 5개이고 최대 처리할 아이템 개수는 10인 쓰레드풀 생성ThreadPool p =newThreadPool(5,10);
p.toggleDebugWithQueue(true);// 아이템 큐 콘솔 디버깅 ON
p.toggleDebugWithRunnable(true);// 쓰레드 콘솔 디버깅 ON// TEST CODEtry{// 아이템 100개를 쓰레드풀에 삽입 및 실행for(int i =0; i <100; i++){Runnable r =newTestRunnable(i);
p.execute(r);}}catch(Exception e){
e.printStackTrace();}// 아이템이 처리됐든 안됐든 일단 쓰레드풀 정지try{
p.stop();}catch(InterruptedException e){System.out.println("ThreadPool is stopped.");}}}// 테스트할 용도의 Runnable 클래스classTestRunnableimplementsRunnable{int index;publicTestRunnable(int i){
index = i;}@Overridepublicvoid run(){System.out.println(index);}}
프로그램을 실행시켜보면 작동하는것을 콘솔로 볼 수 있습니다. 정지 명령이 내려와도 하던 일은 마치고 죽는 아름다운 쓰레드들입니다. (--;)
...(생략)
enqueue adding...
enqueue adding...
ThreadPoolRunnable[0] is stopped.
ThreadPoolRunnable[1] is stopped.
ThreadPoolRunnable[2] is stopped.
ThreadPoolRunnable[3] is stopped.
ThreadPoolRunnable[4] is stopped.
ThreadPoolRunnable[1] is working.
dequeue notifyall...
dequeue removing...
90
ThreadPoolRunnable[1] is dead.
ThreadPoolRunnable[0] is working.
dequeue removing...
91
ThreadPoolRunnable[0] is dead.
ThreadPoolRunnable[3] is working.
dequeue removing...
92
ThreadPoolRunnable[2] is working.
dequeue removing...
ThreadPoolRunnable[4] is working.
dequeue removing...
94
ThreadPoolRunnable[4] is dead.
93
ThreadPoolRunnable[2] is dead.
ThreadPoolRunnable[3] is dead.
5. java.util.concurrent package
하지만 위처럼 직접 구현 방법 외에도 java.util.concurrent 패키지를 이용해 정말로 간단한 쓰레드풀을 구현 할 수 있습니다.
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadFactory;publicclassTestMain{staticint index =0;publicstaticvoid main(String[] args){// 새로운 쓰레드 추가시 출력 문구를 위한 ThreadFactoryThreadFactory tf =newThreadFactory(){@OverridepublicThread newThread(Runnable r){Thread thread =newThread(r);System.out.println("Runnable"+"["+ index+++"]"+" is created.");return thread;}};// 쓰레드를 5개까지만 만드는 쓰레드풀 생성ExecutorService p =Executors.newFixedThreadPool(5, tf);// TEST CODEtry{// 아이템 100개를 쓰레드풀에 삽입 및 실행for(int i =0; i <100; i++){Runnable r =newTestRunnable(i);
p.execute(r);}}catch(Exception e){
e.printStackTrace();}
p.shutdown();}}// 테스트할 용도의 Runnable 클래스classTestRunnableimplementsRunnable{int index;publicTestRunnable(int i){
index = i;}@Overridepublicvoid run(){System.out.println(index);}}
참 쉽죠? :)
부연 설명을 조금 넣자면 .newFixedThreadPool()로 반환 받는 객체는 ThreadPoolExecutor 클래스입니다. 이 클래스에서는 초기화 할 때에 LinkedBlockingQueue<Runnable>라는 내부 큐를 이용하게 되어 있습니다. 단지 외부에 노출을 안할 뿐이에요. :3