본문 바로가기
JAVA/ETC

Java Executor(Thread 생성, 관리)

by pms93 2024. 2. 27.

프로젝트 중 Java API Method의 프로세스 유효 시간을 적용해야 할 일이 생긴적이 있었다. 해서 공부를 해봤는데...

 

Java에서는 Executor, ExecutorService 객체를 통해 Thread를 생성/관리를 할 수 있었다.

 

종류는 크게 3가지가 존재한다.

CachedThreadPool
 - Thread Caching
 - 60초동안 작업 없으면 Thread Pool에서 제거

FixedThreadPool
 - 고정된 Thread 개수를 가진다. - fixedThreadPool을 생성 할 때 해당 머신의 CPU 코어수를 기준으로 생성 시 더 좋은 performance를 얻을 수 있다고 한다.

SingleThreadExecutor
 - 한 개의 Thread로 작업을 처리

 

package timeoutTest;

import java.util.concurrent.*;

public class TimeoutTest1 {
    // fixedThreadPool 생성 갯수
    public static int CONF_THREADCNT = 2;

    // time out cofig value
    public static Long CONF_TIMEOUT = 5000L;

    // 테스트용 Thread Sleep time
    public static Long CONF_PROCESSSLEEPTIME = 1000L;

    // time out Messagae
    public static String TIMEOUTMSG = "time over...";

    public static void main(String[] args) throws Exception {
        /*
            CachedThreadPool
             - Thread Caching
             - 60초동안 작업 없으면 Thread Pool에서 제거

            FixedThreadPool
             - 고정된 Thread 개수를 가진다.
             - fixedThreadPool을 생성 할 때 해당 머신의 CPU 코어수를 기준으로 생성 시 더 좋은 performance를 얻을 수 있음.

            SingleThreadExecutor
             - 한 개의 Thread로 작업을 처리
        */
        TimeoutTest1 t = new TimeoutTest1();

        ExecutorService cachedThreadPool = t.getCachedThreadPool();
        ExecutorService fixedThreadPool = t.getFixedThreadPool();
        ExecutorService singleThreadPool = t.getSingleThreadPool();

        String result;
        int processResult;

        t.doTest1(cachedThreadPool);
        // 하위 문자열 출력을 통해 doTest1 method는 submit method의 process 종료를 기다리지 않음을 알 수 있다.
        System.out.println("기다리지 않는다.");

        result = t.doTest2(cachedThreadPool);
        // 하위 result 변수값 출력을 통해 doTest2 method는 submit method process 종료를 기다리는것을 알 수 있다.
        System.out.println(result);

        try {
            t.doTest3(cachedThreadPool);
        } catch (Exception e) {
            /*
                doTest3 process 중 timeout 발생 시 throws Exception으로 인해 해당 catch문으로 빠지게 된다.
                일반적으로 생각했을 경우 Exception이 발생하면 해당 process는 종료되어야 하나 실행시켜보면 종료되지 않고 Runnable에 정의된 process가 계속 실행됨을 알 수 있다. (doTest4 참조)
            */
            System.out.println(TIMEOUTMSG);
        }

        try {
            t.doTest4(cachedThreadPool);
        } catch (InterruptedException ie) {
            System.out.println("여기로 오나?");
        } catch (Exception e) {
            // time out 발생 시 process를 종료시키기 위한 방법

            /*
                shutdown();
                 - 현재 process중인 task는 계속 진행 후 종료하며 새로운 task는 수락하지 않는다.
                 - 혼동 주의) cachedThreadPool의 task를 저지 할 뿐, 실행시켜보면 doTest4 method의 호출은 막을 수 없다.
                             shutdown, shutdownNow는 blocking을 발생시키지 않는다. 즉, 이후 정의된 프로세스는 Callable(혹은 Runnable)프로세스의 상태와 상관 없이 진행된다.
                 - shutdown method 사용 중 현재 task 종료를 기다리기 위한(혹은 다른 이유로) blocking이 필요한 상황이라면 awaitTermination(long timeout, TimeUnit unit) 사용한다.

                * awaitTermination
                 - 현재 task가 timeout 내에 프로세스 되었다면 true, 아니면 false를 return한다.
            */
            cachedThreadPool.shutdown();

            // 아래 출력문을 통해 blocking이 발생되지 않음을 확인할 수 있음.
            System.out.println(TIMEOUTMSG);

            // shutdown 이후 재호출 시 RejectedExecutionException이 발생한다.
            t.doTest4(cachedThreadPool);

            /*
                shutdownNow();
                 - 현재 thread를 바로 중단시킨다. current Thread interrupt와 동반되어야 하며 doTest4 참고
            */
        }
    }

    public void doTest1(ExecutorService cachedThreadPool) throws Exception {
        System.out.println("doTest1 started");

        cachedThreadPool.submit(() -> {
            // Callable 객체를 인스턴스 하여 submit의 parameter에 제공되어야 하며 이와 같이 Lambda Expression으로 간략하게 표현 가능하다.
            // submit method를 호출하면 doTest1 method는 submit method의 process 종료를 기다리지 않고 비동기로 진행된다.

            int cnt = 1;

            try {
                while (cnt <= 6) {
                    System.out.println(cnt + " Seconds...");

                    processSleep(CONF_PROCESSSLEEPTIME);

                    cnt++;
                }
            } catch (Exception e) {
                e.printStackTrace();

                return "process failed";
            }

            return "process success";
        });

        System.out.println("doTest1 ended");
    }

    public String doTest2(ExecutorService cachedThreadPool) throws Exception {
        System.out.println("doTest2 started");

        String result;

        result = cachedThreadPool.submit(() -> {
            int cnt = 1;

            try {
                while (cnt <= 6) {
                    System.out.println(cnt + " Seconds...");

                    processSleep(CONF_PROCESSSLEEPTIME);

                    cnt++;
                }

                return "process success";
            } catch (Exception e) {
                e.printStackTrace();

                return "process failed";
            }
        }).get();
        //    ↖ submit method는 Future Class를 반환하며 이를 통해 get method를 호출 할 수 있다.
        //      get method를 호출하게 되면 doTest2 method는 submit method process 종료를 기다린다.
        //      parameter를 제공하여 해당 process의 time out을 설정 할 수 있다. (doTest3 참조)

        System.out.println("doTest2 ended");
        return result;
    }

    public void doTest3(ExecutorService cachedThreadPool) throws Exception {
        System.out.println("doTest3 started");

        cachedThreadPool.submit(() -> {
            int cnt = 1;

            try {
                while (cnt <= 6) {
                    System.out.println(cnt + " Seconds...");

                    processSleep(CONF_PROCESSSLEEPTIME);

                    cnt++;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).get(CONF_TIMEOUT, TimeUnit.MILLISECONDS);
        // submit method의 time out은 5초로 설정되어 있는 상태이며 Runnable에 정의된 process는 총 6초 정도가 걸리는 상황.

        System.out.println("doTest3 ended");
    }

    public void doTest4(ExecutorService cachedThreadPool) throws Exception {
        System.out.println("doTest4 started");

        try {
            cachedThreadPool.submit(() -> {
                Thread t = Thread.currentThread();

                int cnt = 1;

                try {
                    while (!t.isInterrupted()) {
                        if (cnt > 6) {
                            break;
                        }

                        System.out.println(cnt + " Seconds...");

                        processSleep(CONF_PROCESSSLEEPTIME);

                        cnt++;
                    }
                } catch (InterruptedException ie) {
                    System.out.println("current Thread is interrupted");

                    t.interrupt();
                } catch (Exception e) {
                    System.out.println("ee");
                    e.printStackTrace();
                }
            }).get(CONF_TIMEOUT, TimeUnit.MILLISECONDS);
        } catch (TimeoutException te) {
            System.out.println("te");

            /*
                shutdownNow();
                 - shutdown과는 다르게 현재 수행중인 task
                 - Executors로 생성한 thread는 none-daemon thread이므로 이를 수동으로 종료 시켜야 한다.
                 - Thread interrupt를 이용한 강제 종료가 구현되지 않으면 현 task에 대한 강제 종료를 보장 할 수 없다.
            */
            cachedThreadPool.shutdownNow();
            Thread.currentThread().interrupt();
        } catch (InterruptedException ie) {
            System.out.println("ie");
        } catch (Exception e) {
            System.out.println("e");
        }

        System.out.println("doTest4 ended");
    }

    public ExecutorService getCachedThreadPool() throws Exception {
        return Executors.newCachedThreadPool();
    }

    public ExecutorService getFixedThreadPool() throws Exception {
        return Executors.newFixedThreadPool(CONF_THREADCNT);
    }

    public ExecutorService getSingleThreadPool() throws Exception {
        return Executors.newSingleThreadExecutor();
    }

    public void processSleep(Long time) throws Exception {
        Thread.sleep(time);
    }
}

'JAVA > ETC' 카테고리의 다른 글

디버그(Debug)  (0) 2022.07.21
주석  (0) 2022.07.15