Synchronized로 검색한 결과 :: 시소커뮤니티[SSISO Community]
 
SSISO 카페 SSISO Source SSISO 구직 SSISO 쇼핑몰 SSISO 맛집
추천검색어 : JUnit   Log4j   ajax   spring   struts   struts-config.xml   Synchronized   책정보   Ajax 마스터하기   우측부분

회원가입 I 비밀번호 찾기


SSISO Community검색
SSISO Community메뉴
[카페목록보기]
[블로그등록하기]  
[블로그리스트]  
SSISO Community카페
블로그 카테고리
정치 경제
문화 칼럼
비디오게임 스포츠
핫이슈 TV
포토 온라인게임
PC게임 에뮬게임
라이프 사람들
유머 만화애니
방송 1
1 1
1 1
1 1
1 1
1

Synchronized로 검색한 결과
등록일:2018-05-07 17:20:44
작성자:
제목:알기 쉽게 설명한 Thread Pooling 에 대한 패턴


Thread Pool 을 만들어쓰고 싶다...
초급 개발자라면 Thread 도 아리송한데... Pooling 이라니.. 어려워*3 할지 모르겠다..
조금 쉽게 Thread Pooling 에 대해 설명하고자 가장 유사한 예시를 생각해보니 '인력 파견 용역 회사' 였다...
이제부터 피도 눈물도 없는(^^;) 인력 파견 용역 회사 사장의 입장이 되어본다.

사업계획서를 작성하니
성수기때는 25건 정도, 비수기때는 5건 정도, 평균 10건 정도의 일감이 있는 것으로 조사되었다.
그런데 국가에서 20인 이하 사업자에게 주는 세금혜택이 있다고 한다.
그래서...
회사 규모를 평시에는 10명정도로 운영하다가 성수기때는 최대 20인까지 충원하여 운영한다.
사무실에는 의자를 10개 비치하고 일이 없는 동안 대기토록 하고 일감이 들어오면 파견을 보낸다.
일을 마치고 복귀할때 비어있는 의자가 없는 직원은 서서 기다려야 한다.
서서 기다리는 직원은 일정시간이 지날동안 일을 받지 못하면 퇴사하는 경향이 있다.
처음 창립시에는 5명만 우선 고용하고, 실시간으로 총직원수와 파견나가 있는 직원수를 관리하고자 한다.

위 내용에 대한 코드를 아래처럼 만들어본다.

public class ThreadPool {
    private String name;      //회사명
    private Stack queue;      //사무실
    private int idleCnt;      //대기의자수
    private int maxCnt;       //최대직원수
    private int initCnt;      //초기직원수
    private int idleTimeout;  //서서대기시간
    private int currCnt;      //현재 총 직원수
    private int busyCnt;      //현재 파견나가 일하고 있는 직원수

    public ThreadPool() {
        this.name="인력파견용역회사";
        this.maxCnt = 20;
        this.idleCnt = 10;
        this.initCnt = 5;
        this.idleTimeout = 3000;
        this.currCnt = 0;
        this.busyCnt = 0;
        this.queue = new Stack();
    }
}

회사는 직원에게 채용시 사번을 부여하며, 파견을 보낼 수 있다.
회사의 규정에 따라 직원은 퇴사시 사직서를 내야 한다(현재 직원수 관리상의 이유로).

아래는 직원에 대한 코드이다.

public class 
    class CachedThread extends Thread {
    private ThreadPool pool;   // 회사
    private boolean alive;     // 재직중 여부 
    private Runnable runner;   // 할당된 일

    // 신규 채용
    CachedThread(String name,ThreadPool pool) {
        super(name);
        this.pool = pool;
        this.alive = true;
    }
    
    // 근무
    public void run() {
        try {
            // 반복되는 일상....
            while ( true ) {
                orderOrWait();             // 파견 혹은 대기.. (뒤에 구현)
                if ( !this.alive ) break;  // 재직중인 아니면, 일상에서의 탈출이다.
            }
        } finally {
            // 나가기전에 사직서는 내고 나간다.
            pool.noticeOfDead();
        }
    }

    // 회사로 부터 업무를 받았다.
    Synchronized void wakeup(Runnable runnable) {
        if ( this.alive ) {         // 당연 재직중일때만...
            this.runner = runnable; // 일감을 받고...
            notify();               // 기다림에서의 해방...
        }
    }
}

퇴사 신고에 대한 코드를 ThreadPool 소스에 추가한다.

    private void noticeOfDead() {
        Synchronized (queue) {
            if ( currCnt>0 ) currCnt--; // 현재직원수 감소..
        }
    }

이제 회사를 운영하는데 필요한 기본 업무를 정리한다.
창업 : 사무실을 얻고, 초기직원을 채용한다.
파견 : 일감이 들어오면 인력을 파견보낸다.
복귀 : 파견업무를 마치고 복귀한다.
채용 : 필요한 인력을 신규 채용한다.

창업과정에서 채용이 필요하기 때문에 먼저 채용에 대한 코드를 ThreadPool 소스에 추가한다.

    private int seq = 0;  // 사번 채번용 일련번호를 멤버변수로 추가..

    // 채용
    private void createThread() {
       // 사번을 부여한 직원을 신규 채용.
       Thread thread = new CachedThread(this.name+(seq++),this);
       thread.start();
       currCnt++; // 현재 직원수 증가..
    }

창업에 대한 코드를 ThreadPool 소스에 추가한다

    // 창업
    public void start() {
        // 창립멤버를 모으자...
        for (int i=0; i<this.initCnt; i++) {
            Synchronized(queue) {
                createThread();
                // 일단 채용은 했는데 이놈이 일할 준비가 될때까지 기다린다.
                try { queue.wait(); } catch (InterruptedException ignored) {}
            }
        }
    }

일감이 들어오면 사무실에서 대기중인 직원을 파견보낸다.
만일 대기중인 직원이 없다면 충원한다. 단 현재직원수는 총직원수보다 많을 수는 없다.
만일 현재직원수가 총직원수보다 많다면 파견나간 직원이 복귀할때 까지 기다린다.
파견에 대한 코드를 ThreadPool 소스에 추가한다.

    // 일감이 들어왔다..
    public void runIt(Runnable runnable) {
        CachedThread thread = getThread();  // 직원을 선정하여 파견보내고 
        busyCnt++;                          // 현재 일하고 있는 직원수 증가...
        thread.wakeup(runnable);            // 파견나간 직원에게 일감을 주고 일을 시킨다.
    }

    // 파견
    private CachedThread getThread() {
        Synchronized(queue) {
            // 일할 직원이 없으면, 생길때까지 기다린다.
            while ( queue.isEmpty() ) {
                // 현재직원수가 최대직원수보다 작으면 신규 채용한다.
                if ( currCnt < maxCnt ) {
                    createThread();
                }
                // 사무소에 대기 인력의 변화가 있을때까지 기다린다.
                try { queue.wait(); } catch (InterruptedException ignored) {}
            }
            // 대기 중인 직원 한명을 선발해서 파견보낸다.
            return (CachedThread) queue.pop();
        }
    }

직원은 일을 마치고 복귀하거나, 신규 채용되어 일을 시작할 준비가 되면 대기 신고를 한다.
만일 복귀했는데 의자수보다 대기 직원이 많다면 일정 시간동안 서서 대기하게 된다.
대기 신고에 대한 코드를 ThreadPool 소스에 추가한다.

    // 대기 신고...
    private int noticeOfIdle(CachedThread thread) {
        if ( busyCnt>0 ) busyCnt--; // 현재 일하고 있는 직원수 감소..
        Synchronized (queue) {
            queue.push(thread);  // 사무실로 복귀...
            queue.notify();  // 복귀했다고 사무실에 알린다.
        }
        // 현재 직원수가 대기의자수보다 적으면 앉아서 대기(0)
        // 현재 직원수가 대기의자수보다 많으면 특정 시간 만틈 서서 대기.
        return (currCnt <= idleCnt) ? 0 : idleTimeout;
     }

직원은 재직중에 파견나가서 일을 하고 복귀했을때 사무소의 의자에서 대기한다.
앉을 의자가 없다면 특정 시간만큼은 서서 대기하는데, 그 때까지 일이 없으면 바로 퇴사한다.
직원의 근무에 대한 코드를 CachedThread 소스에 추가한다.

    // 직원의 근무
    Synchronized void orderOrWait() {
        while ( alive ) {
            // 일감이 있으면 일하고...
            if ( this.runner != null ) {
                this.runner.run();
                this.runner = null;
            // 일감이 없으면 사무실로 복귀..
            } else {
                try {
                    int time = pool.noticeOfIdle(this); // 대기신고를 한다.
                    if ( time > 0 ) {
                        wait(time);  // 특정 시간만큼 서서 대기...
                        this.alive = (this.runner!=null); // 시간이 지나고 나서도 일감이 없으면 퇴사한다.
                    } else if ( time == 0 ) {
                        wait();  // 다음 일이 있을 때까지 계속 대기.
                    }
                } catch (InterruptedException ex) {}
            }
        }
    }

일감을 받아 처리하는 구성은 모두 마쳤다.
이제는 회사가 망했을 경우, 즉 폐업할 경우를 생각해본다.
폐업하더라도 일단 기존에 진행하고 있는 일은 끝까지 진행하는 것을 원칙으로 한다.
파견나가 있는 직원은 일을 모두 마치고 대기 신고를 할때 회사가 페업한다는 소식을 듣게되고 퇴사한다.
대기중인 직원은 회사로부터 해고 통지를 받고 퇴사한다.
회사는 모든 직원이 퇴사를 하면 그때 문을 닫는다.

회사의 폐업 통보를 받은 대기 직원에 대한 코드를 CachedThread 소스에 추가한다.

    // 회사 폐업 통보를 받으면...
    Synchronized void terminate() {
        alive = false;  // 이제는 재직중이 아니며..
        notify(); // 더이상 대기할 필요가 없다.
    }

회사의 폐업에 대한 코드를 ThreadPool 소스에 추가한다.

    private boolean stoped = false;  // 폐업 여부 상태 정보를 멤버변수로 추가..

    // 폐업
    public void stop() {
        this.stoped = true;
        Synchronized (queue) {
            // 본사 대기중인 직원이 있다면 폐업 통보를 한다.  
            while (!queue.isEmpty()) {
                CachedThread thread = (CachedThread)queue.pop();
                thread.terminate();
            }
        }
        // 모든 직원들이 하던 일 마무리짓고 나갈때까지는 버티자...
        while(!(this.currCnt==0));
    }

회사의 폐업에 대해 일을 마치고 복귀하는 직원에 대한 코드를 ThreadPool 소스에 추가한다.
앞서 작성된 대기 신고 noticeOfIdle 메쏘드를 수정한다.

    // 대기 신고...
    private int noticeOfIdle(CachedThread thread) {
        if ( busyCnt>0 ) busyCnt--; // 현재 일하고 있는 직원수 감소..
        
        // 폐업했으니 대기할 필요 없다고 알려줌..(-1)
        if ( stoped ) return -1;

        Synchronized (queue) {
            queue.push(thread);  // 사무실로 복귀...
            queue.notify();  // 복귀했다고 사무실에 알린다.
        }
        // 현재 직원수가 대기의자수보다 적으면 앉아서 대기(0)
        // 현재 직원수가 대기의자수보다 많으면 특정 시간 만틈 서서 대기.
        return (currCnt <= idleCnt) ? 0 : idleTimeout;
     }

대기 신고시에 회사의 폐업 통보를 알게된 직원에 대한 코드를 CachedThread 소스에 추가한다. 앞서 작성된 직원의 근무 orderOrWait 메쏘드를 수정한다.

    // 직원의 근무
    Synchronized void orderOrWait() {
        while ( alive ) {
            // 일감이 있으면 일하고...
            if ( this.runner != null ) {
                this.runner.run();
                this.runner = null;
            // 일감이 없으면 사무실로 복귀..
            } else {
                try {
                    int time = pool.noticeOfIdle(this); // 대기신고를 한다.
                    if ( time > 0 ) {
                        wait(time);  // 특정 시간만큼 서서 대기...
                        this.alive = (this.runner!=null); // 시간이 지나고 나서도 일감이 없으면 퇴사한다.
                    } else if ( time == 0 ) {
                        wait();  // 다음 일이 있을 때까지 계속 대기.
                    } else {
                        // 페업했으니 재직중도 아니고 대기할 필요도 없다.
                        this.alive = false;
                    }
                } catch (InterruptedException ex) {}
            }
        }
    }

이제 모든 코드를 마쳤고 테스트를 위해 간단하게 main 프로그램을 만들어 실행해 본다...

    public static void main(String[] args) { 
        ThreadPool pool = new ThreadPool(); 
        pool.start(); 
        for(int i=0; i<30; i++) { 
            pool.runIt(new Runnable() { 
                public void run() { 
                    System.out.println("-----업무진행"); } 
                }
            ); 
        } 
        pool.stop(); 
    } 

기본적인 형태의 Thread Pooling 기법에 대해 알아보았다.
하지만 위 소스는 단시간에 간단하게 만들어본것이기 때문에 중간중간 누수가 있을 수 있음을 밝힌다.
실무에 적용하기 위해서는 위의 코드를 바탕으로 구성하고 심도있게 테스트와 디버깅을 충분히 해야 할 필요가 있을 것이다