개발 무지렁이

[Java] 스레드 개수를 제한하는 스레드풀(ThreadPool)의 생성과 작업처리 요청 본문

Backend/자바

[Java] 스레드 개수를 제한하는 스레드풀(ThreadPool)의 생성과 작업처리 요청

Gaejirang-e 2023. 8. 14. 12:49

𐂂 스레드(Thread) 개수가 폭증하면 CPU가 바빠지고 메모리 사용량이 늘어난다.
병렬작업 증가로 인한 스레드 폭증을 막으려면 스레드풀(ThreadPool)을 사용하는 것이 좋다
스레드풀(ThreadPool)은 작업처리에 사용되는 스레드 개수를 제한해 놓고,
작업 큐(Queue)에 들어오는 작업들을 처리하는 방식을 말한다.
작업량이 증가해도 스레드 개수가 늘어나지 않아 성능저하를 막을 수 있다.

𐁍 스레드풀(ThreadPool) 생성
java.util.concurrent 패키지에서
Executors 클래스정적메서드를 이용하여,
ExecutorService 인터페이스를 구현한 구현객체를 만들어 생성한다.

🌵 Executors 클래스의 정적메서드

- newCachedThreadPool()
: 작업개수가 많아지면, 새 스레드를 생성해 작업을 처리한다.
(60초 동안 스레드가 아무 작업을 하지 않으면, 스레드 제거)

- newFixedThreadPool(int nThreads)
: 작업개수가 많아지면, 최대 nThreads개까지 스레드를 생성해 작업을 처리한다.
(생성된 스레드를 제거 ❌)

🏈 ThreadPoolExecutor 생성자를 호출
new ThreadPoolExecutor(
                3, //코어 스레드 수: 스레드 증가 후 스레드풀에서 유지하는 최소한의 스레드 수
                100, //최대 스레드 수
                120L, //놀고 있는 최대 시간
                TimeUnit.SECONDS, //놀고 있는 시간 단위
                new SynchronousQueue<Runnable>() //작업 큐
);

🦔 하나의 작업은 Runnable or Callable 구현객체로 표현된다.
RunnableCallable의 차이는, 작업 처리 완료후 리턴값의 여부이다.

🎭 Runnable 익명구현객체
new Runnable() {
    @Override
    public void run() {

    }
}

🎭 Callable 익명구현객체

new Callable<T>() {
    @Override
    public T call() throws Exception {

        return T;
    }
}

𐁍 작업처리요청이란 ExecutorService의 작업 큐에 Runnable or Callable 객체를 넣는 행위를 말한다.
objExecutorSerivce.execute(Runnable cmd)
: Runnable작업 큐(Queue)에 저장

objExecutorService.submit(Callable task)
: Callable작업 큐(Queue)에 저장
: 작업처리결과 Future 리턴

𖠃 메인 스레드가 종료되더라도 스레드풀은 RUNNIG으로 남아있다.
shutdown():
작업 큐에 대기하고 있는 모든 작업을 처리한 뒤에 스레드풀 종료

shutdownNow():
현재 처리중인 스레드를 interrupt해서 중지시키고, 스레드풀 종료
return (List<Runnable>)미처리된 작업(in 작업 큐)

🏓 1000개의 이메일 보내는 작업을 작업 큐에 넣고, 스레드풀의 최대 5개의 스레드로
   작업큐에서 Runnable 객체를 하나씩 꺼내서 작업 처리


📜 RunnableExecuteExample.java

  public class RunnableExecuteExample {
      public static void main(String[] args) {
          String[][] mails = new String[1000][3];
          for(int i = 0; i < mails.length; i++) {
              mails[i][0] = "admin@my.com";
              mails[i][1] = "member"+i+"@my.com";
              mails[i][2] = "신상품 입고";
          }

          ExcecutorService executorService = Excecutors.newFixedThreadPool(5);

          for(int i = 0; i < 1000; i++) {
              final int idx = i;
              executorService.execute(new Runnable() {
                  @Override
                  public void run() {
                      Thread thread = Thread.currentThread();
                      String from = mails[idx][0];
                      String to = mails[idx][1];
                      String content = mails[idx][2];

                      System.out.println("[" + thread.getName() + "]" +
                                  from + " ==> " + to + ": " + content);
                  }
              });
          }

          executorService.shutdown(); //스레드풀 실행 종료

          //[pool-1-thread-1] admin@my.com ==> member6@my.com: 신상품 입고
          //[pool-1-thread-4] admin@my.com ==> member3@my.com: 신상품 입고
          //[pool-1-thread-3] admin@my.com ==> member2@my.com: 신상품 입고
          //[pool-1-thread-2] admin@my.com ==> member1@my.com: 신상품 입고
          //[pool-1-thread-2] admin@my.com ==> member10@my.com: 신상품 입고
          //... 
      }
  }

🏓 100개의 1~n까지의 합을 구하는 작업을 작업 큐에 넣고, 스레드풀의 최대 5개의 스레드로
   작업큐에서 Callable 객체를 하나씩 꺼내서 작업 처리


📜 CallableSubmitExample.java

  public class CallableSubmitExample [
      public static void main(String[] args) {
          ExecutorService executorService = Executors.newFixedThreadPool(5);

          for(int i = 1; i <= 100; i++) {
              final int idx = i;
              Future<Integer> future = executorSerivce.submit(new Callable<Integer>() {
                  @Override
                  public integer call() throws Exception {
                      int sum = 0;
                      for(int i = 1; i <= idx; i++) {
                          sum += i;
                      }
                      Thread thread = Thread.currentThread();
                      System.out.pritnln("[" + thread.getName() + "] 1~" + idx + " 합 계산");
                      return sum;
                  }
              });

              try {
                  int result = future.get(); //call() 메서드의 리턴값 얻기
                  System.out.println("\t리턴값: " + result);
              } catch(Exception e) {
                  e.printStackTrace();
              }
          }

          executorService.shutdown();

          //...
          //[pool-1-thread-3] 1~98 합 계산
          //    리턴값: 4851
          //[pool-1-thread-4] 1~99 합 계산
          //    리턴값: 4950
          //[pool-1-thread-5] 1~100 합 계산
          //    리턴값: 5050 
      }
  }
Comments