본문 바로가기
네이버클라우드/JAVA 웹 프로그래밍

JAVA 39일차 (2023-07-17) 자바 프로그래밍_스레드 풀 사용법

by prometedor 2023. 7. 17.
- 자바 프로그래밍
  - 스레드 프로그래밍(com.eomcs.concurrent.ex7)
    - 스레드풀 사용법

 

Executors 태스크 프레임워크 - 스레드풀 만들고 사용하기

Exam0110.java

// Executors 태스크 프레임워크 - 스레드풀 만들고 사용하기
package com.eomcs.concurrent.ex7;

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

public class Exam0110 {

  public static void main(String[] args) {
    // 스레드풀을 생성한다.
    // - 최대 3개의 스레드를 생성한다.
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    // 스레드풀에 작업 수행을 요청한다.
    // - 작업은 Runnable 구현체로 작성하여 넘겨준다.
    // - 스레드풀은 스레드를 생성하여 작업을 수행시킨다.
    executorService.execute(
        () -> System.out.printf("%s - Hello!\n", Thread.currentThread().getName()));

    System.out.println("main() 종료!");
    // JVM은 main 스레드를 종료하더라도 나머지 스레드가 종료할 때까지 기다린다.
    // 스레드풀에서 생성한 스레드가 요청한 작업을 마치더라도
    // 다음 작업을 수행하기 위해 계속 실행된 채로 대기하고 있기 때문에
    // JVM은 종료하지 않는다.
  }
}

ㄴ 스레드 풀을 실행해놓고 메인 스레드가 종료됨

=> 스레드 풀에서 생성한 스레드가 요청한 작업을 마치더라도 다음 작업을 수행하기 위해 계속 실행된 채로 대기하고 있으므로 JVM 은 종료하지 않음

 

=>

ㄴ 익명 클래스 람다로 바꾸기

    ㄴ 메소드 바깥 껍데기(new Runnable() { ... })를 제거

    ㄴ 메서드 이름(run())을 제거

    ㄴ 파라미터와 메서드 바디 사이에 -> 추가

    ㄴ 파라미터 타입 삭제(위 코드에는 파라미터 없으므로 그대로)

    ㄴ 파라미터가 없을 경우 괄호 생략 불가

    ㄴ 문장이 하나 일 경우 {} 중괄호 제거 가능

=>

ㄴ 계속 실행된 채로 대기하고 있으므로 강제종료 필요

 

 

Executors 태스크 프레임워크 - 스레드풀 종료하기

Exam0120.java

// Executors 태스크 프레임워크 - 스레드풀 종료하기
package com.eomcs.concurrent.ex7;

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

public class Exam0120 {

  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    executorService.execute( 
        () -> {
          System.out.printf("%s - Hello!\n", Thread.currentThread().getName());

          try { 
            Thread.sleep(10000); // 현재 스레드를 10초 동안 멈춘다.
          } catch (Exception e) {}

          System.out.printf("%s 스레드 종료!\n", Thread.currentThread().getName());
        });

    // 스레드풀에 있는 모든 스레드들이 요청한 작업을 끝내면 종료하도록 지시한다.
    // 모든 스레드가 종료될 때까지 기다리지 않고 바로 리턴한다.
    // shutdown() 호출 이후에는 새 작업 요청은 받지 않는다.
    // 즉 execute()를 호출하면 예외가 발생한다.
    executorService.shutdown();

    System.out.println("main() 종료!");
  }
}

ㄴ 스레드풀의  shutdown 은 지금 당장 종료하라는 것이 아님 => 종료 요청을 접수할 뿐

     => 스레드풀에 있는 모든 스레드들이 요청한 작업을 끝내면 종료하도록 함

ㄴ shutdown() 호출 이후에는 새 작업 요청을 받지 않음

ㄴ 유지하고 있는, 실행중인 스레드가 없다면 스레드 풀을 가동하지 말아라

=>

=>

ㄴ 10초 후 종료됨

 

 

Executors 태스크 프레임워크 - 스레드풀 만들기 : 고정크기 스레드풀

Exam0210.java

// Executors 태스크 프레임워크 - 스레드풀 만들기 : 고정크기 스레드풀
package com.eomcs.concurrent.ex7;

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

public class Exam0210 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("[%s] - 스레드에서 작업 실행 중...\n",
            Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("[%s] - 작업 종료 후 스레드 대기!\n",
            Thread.currentThread().getName());
      } catch (Exception e) {
        System.out.printf("[%s] 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    // 일단 스레드풀의 크기(3 개)만큼 작업 수행을 요청한다.
    // - 작업은 큐에 등록된 순서대로 보관된다.
    // - 스레드풀은 큐에서 작업을 꺼내 스레드에게 일을 시킨다.
    //
    executorService.execute(new MyRunnable(6000));
    executorService.execute(new MyRunnable(3000));
    executorService.execute(new MyRunnable(9000));

    // 스레드풀의 크기를 초과해서 작업 수행을 요청한다면?
    // - 놀고 있는 스레드가 없을 경우, 다른 스레드의 작업이 끝날 때까지 작업큐에 대기하고 있는다.
    // - 작업을 끝낸 스레드가 생기면 큐에서 작업을 꺼내 실행한다.
    //
    executorService.execute(new MyRunnable(2000));
    executorService.execute(new MyRunnable(4000));

    System.out.println("main() 종료!");
  }
}

ㄴ Executors.newFixedThreadPool(고정크기); => 스레드풀의 크기를 고정시킴

ㄴ 스레드풀의 크기를 초과해서 작업을 수행하면 다른 스레드의 작업이 끝날 떄까지 작업큐에 대기하고 있음

ㄴ 놀고 있는 스레드가 생기는 순간 해당 스레드가 작업을 진행함

=>

 

Executors 태스크 프레임워크 - 스레드풀 만들기 : 가변크기 스레드풀

Exam0220.java

// Executors 태스크 프레임워크 - 스레드풀 만들기 : 가변크기 스레드풀
package com.eomcs.concurrent.ex7;

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

public class Exam0220 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("%s 스레드에서 작업 실행 중...\n",
            Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("%s 작업 끝내고 스레드 대기중!\n",
            Thread.currentThread().getName());
      } catch (Exception e) {
        System.out.printf("%s 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) throws Exception {

    // 스레드의 수를 고정하지 않고 필요할 때마다 스레드를 생성하는 스레드풀이다.
    // 물론 작업을 끝낸 스레드는 다시 사용할 수 있도록 pool에 보관한다.
    ExecutorService executorService = Executors.newCachedThreadPool();

    // 놀고 있는 스레드가 없으면 새 스레드를 생성한다.
    //
    executorService.execute(new MyRunnable(6000));
    executorService.execute(new MyRunnable(2000));
    executorService.execute(new MyRunnable(9000));
    executorService.execute(new MyRunnable(1000));

    // 작업을 끝낸 스레드가 생길 때까지 일부러 기다린다.
    //
    Thread.sleep(3000);

    // 그러면 새 스레드를 생성하지 않고
    // 작업을 끝낸 스레드가 요청한 작업을 처리한다.
    //
    executorService.execute(new MyRunnable(4000));

    System.out.println("main() 종료!");
  }
}

ㄴ 스레드풀은 execute()를 호출한 순서대로 작업큐에 작업을 보관함

ㄴ Executors.newCachedThreadPool();  => 스레드의 수를 고정하지 않고 필요할 때마다 스레드를 생성하는 스레드풀
     => 작업을 끝낸 스레드는 다시 사용할 수 있도록 pool에 보관

=>

 

 

Executors 태스크 프레임워크 - 스레드풀 만들기 : 한 개의 스레드를 갖는 스레드풀

Exam0230.java

// Executors 태스크 프레임워크 - 스레드풀 만들기 : 한 개의 스레드를 갖는 스레드풀
package com.eomcs.concurrent.ex7;

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

public class Exam0230 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("%s 스레드 실행 중...\n",
            Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("%s 스레드 종료!\n",
            Thread.currentThread().getName());
      } catch (Exception e) {
        System.out.printf("%s 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) throws Exception {

    // 한 개의 스레드만 갖는 스레드풀이다.
    ExecutorService executorService = Executors.newSingleThreadExecutor();

    // 스레드가 한 개이기 때문에 순차적으로 실행한다.
    executorService.execute(new MyRunnable(6000));
    executorService.execute(new MyRunnable(3000));
    executorService.execute(new MyRunnable(9000));
    executorService.execute(new MyRunnable(2000));
    executorService.execute(new MyRunnable(4000));

    System.out.println("main() 종료!");
  }
}

ㄴ Executors.newSingleThreadExecutor(); => 한 개의 스레드만 갖는 스레드풀임을 알 수 있음

=>

=>

=>

=>

=>

=>

ㄴ 모든 작업을 하나의 스레드만을 이용해서 함

 

Executors 태스크 프레임워크 - 작업 실행 : execute()

Exam0310.java

// Executors 태스크 프레임워크 - 작업 실행 : execute()
package com.eomcs.concurrent.ex7;

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

public class Exam0310 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("%s 스레드 실행 중...\n",
            Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("%s 스레드 종료!\n",
            Thread.currentThread().getName());
      } catch (Exception e) {
        System.out.printf("%s 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    // 스레드풀에 수행할 작업을 등록한다.
    // 스레드풀은 execute()를 호출한 순서대로 작업큐에 작업을 보관한다.
    // 그리고 놀고 있는 스레드가 있다면, 작업큐에서 작업을 꺼내 수행시킨다.
    // 놀고 있는 스레드가 없으면, 새로 스레드를 생성한다.
    // 스레드가 최대 개수라면 기존 스레드가 작업을 끝낼 때까지 기다린다.
    // => 수행한 작업의 종료 여부를 확인할 수 없다.
    executorService.execute(new MyRunnable(6000));

    System.out.println("main() 종료!");
  }
}

수행한 작업의 종료 여부를 확인할 수 없음

=>

=>

ㄴ 6초 후 깨어남

 

Executors 태스크 프레임워크 - 작업 실행 : submit()

Exam0320.java

// Executors 태스크 프레임워크 - 작업 실행 : submit()
package com.eomcs.concurrent.ex7;

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

public class Exam0320 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("%s 스레드 실행 중...\n",
            Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("%s 스레드 종료!\n",
            Thread.currentThread().getName());
      } catch (Exception e) {
        System.out.printf("%s 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) throws Exception {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    // execute()와 같다.
    // => 단 작업의 종료 상태를 확인할 수 있는 Future 객체를 리턴한다.
    //
    Future<?> future1 = executorService.submit(new MyRunnable(2000));
    Future<?> future2 = executorService.submit(new MyRunnable(4000));

    // Future.get()
    // => 요청한 작업이 완료될 때 까지 기다린다.(pending)
    // => 요청한 작업이 완료되면 null을 리턴한다.
    //
    future2.get();
    System.out.println("두 번째 작업이 끝났음");

    future1.get();
    System.out.println("첫 번째 작업이 끝났음");

    System.out.println("main() 종료!");
  }
}

ㄴ Future.get() => 요청한 작업이 완료될 때 까지 기다리고, 요청한 작업이 완료되면 null 리턴

=>

=>

=>

=>

 

Executors 태스크 프레임워크 - 스레드풀 종료 : shutdown()

Exam0410.java

// Executors 태스크 프레임워크 - 스레드풀 종료 : shutdown()
package com.eomcs.concurrent.ex7;

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

public class Exam0410 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("[%d] %s 스레드 실행 중...\n",
            this.millisec, Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("[%d] %s 스레드 종료!\n",
            this.millisec, Thread.currentThread().getName());

      } catch (Exception e) {
        System.out.printf("[%d] %s 스레드 실행 중 오류 발생!\n", 
            this.millisec, Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    executorService.execute(new MyRunnable(6000));
    executorService.execute(new MyRunnable(2000));
    executorService.execute(new MyRunnable(4000));
    executorService.execute(new MyRunnable(4000));
    executorService.execute(new MyRunnable(4000));
    executorService.execute(new MyRunnable(4000));

    // => 더이상 작업 요청을 받지 말고
    //    이전에 요청한 작업(대기하고 있는 작업)들이 완료되면
    //    스레드를 종료하도록 예약한다.
    // => 작업 중인 스레드가 Not Runnable 상태가 아니라면 
    //    작업이 끝날 때까지 기다린다.
    executorService.shutdown();

    // 작업 요청을 거절한다.
    // => 예외 발생!
    executorService.execute(new MyRunnable(4000));

    System.out.println("main() 종료!");
  }
}

ㄴ 위 코드에서는 3개의 스레드가 작업을 함

ㄴ shutdown 을 할 경우 더이상 작업 요청을 받지 않고 이전에 요청한 작업(대기하고 있는 작업)들이 완료되면 스레드를 종료하도록 예약함

ㄴ shutdown 한 다음에는 스레드에 작업을 맡기면 작업 요청을 거절함 => 예외발생

=>

 

Executors 태스크 프레임워크 - 스레드풀 종료 : shutdownNow()

Exam0420.java

// Executors 태스크 프레임워크 - 스레드풀 종료 : shutdownNow()
package com.eomcs.concurrent.ex7;

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

public class Exam0420 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("[%d] %s 스레드 실행 중...\n",
            this.millisec, Thread.currentThread().getName());

//        Thread.sleep(millisec);
            for (long i = 0; i < 1_0000_0000; i++) {
                double r = Math.tan(3456.77889) / Math.random();
        
            }

        System.out.printf("[%d] %s 스레드 종료!\n",
            this.millisec, Thread.currentThread().getName());

      } catch (Exception e) {
        System.out.printf("[%d] %s 스레드 실행 중 오류 발생!\n", 
            this.millisec, Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    executorService.execute(new MyRunnable(1000));
    executorService.execute(new MyRunnable(2000));
    executorService.execute(new MyRunnable(3000));
    executorService.execute(new MyRunnable(7000));
    executorService.execute(new MyRunnable(8000));
    executorService.execute(new MyRunnable(9000));

    // 가능한 현재 수행 중인 작업들을 모두 멈추도록 지시한다.
    // => shutdown()과 차이점:
    //    - 만약 Running 상태의 스레드가 Not Runnable 상태(sleep()/wait())에 놓인다면,
    //      바로 스레드를 멈출 기회라고 보고 스레드를 강제 종료할 것이다.
    //    - 즉 실행 중인 작업만 완료시키고, 대기 중인 작업은 취소시키는 효과가 있다.
    // => Running 상태에 Not Runnable 상태가 될 때까지 기다린다.
    // => 다만 작업을 마치고 대기 중인 스레드는 즉시 취소한다.
    // => 그리고 취소한 대기 작업 목록을 리턴해준다.
    // 
    List<Runnable> tasks = executorService.shutdownNow();
    System.out.println("실행 취소된 작업들:");
    System.out.println("--------------------------------");
    for (Runnable task : tasks) {
      System.out.println(((MyRunnable) task).millisec);
    }
    System.out.println("--------------------------------");

    // 물론 새 작업 요청도 거절한다.
    // => 예외 발생!
    executorService.execute(new MyRunnable(4000));

    // shutdown() vs shutdownNow();
    // - shutdown()
    //   진행 중인 작업을 완료하고 대기 중인 작업도 완료한 다음 종료.
    // - shutdownNow()
    //   진행 중인 작업을 즉시 종료하고, 대기 중인 작업은 취소하고 그 목록 리턴한다.
    System.out.println("main() 종료!");
  }
}

ㄴ shutdownNow() 메서드는 스레드 풀에 대기 중인 작업을 무시하고, 현재 실행 중인 작업을 중단시키며 스레드 풀을 종료함

=>

 

 

Executors 태스크 프레임워크 - 스레드풀 종료 대기 : awaitTermination()

Exam0510.java

// Executors 태스크 프레임워크 - 스레드풀 종료 대기 : awaitTermination()
package com.eomcs.concurrent.ex7;

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

public class Exam0510 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("%s 스레드 실행 중...\n",
            Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("%s 스레드 종료!\n",
            Thread.currentThread().getName());
      } catch (Exception e) {
        System.out.printf("%s 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) throws Exception {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    executorService.execute(new MyRunnable(6000));
    executorService.execute(new MyRunnable(2000));
    executorService.execute(new MyRunnable(4000));
    executorService.execute(new MyRunnable(5000));

    executorService.shutdown();

    // 스레드풀의 모든 스레드가 종료되면 즉시 true를 리턴한다.
    // 만약 지정된 시간(예: 10초)이 경과할 때까지 종료되지 않았다면 false를 리턴한다.
    //
    if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
      System.out.println("아직 종료 안된 작업이 있다.");
    } else {
      System.out.println("모든 작업을 종료하였다.");
    }

    System.out.println("main() 종료!");
  }
}

ㄴ 10 => 값

ㄴ TimeUnit.SECONDS => 초

ㄴ 10초 이내라도 종료되는 순간 즉시 리턴

ㄴ 10초 지나도 종료가 안 되면 false 를 리턴함

=>

ㄴ 정상적으로 종료되었을 경우

=>

ㄴ 한 개의 작업을 15초로 설정할 경우 10초 지나도 종료가 안 된 것이 있음을 알 수 있음


Executors 태스크 프레임워크 - 스레드풀 종료 대기 : awaitTermination() 활용

Exam0520.java

// Executors 태스크 프레임워크 - 스레드풀 종료 대기 : awaitTermination() 활용
package com.eomcs.concurrent.ex7;

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

public class Exam0520 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("%s 스레드 실행 중...\n",
            Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("%s 스레드 종료!\n",
            Thread.currentThread().getName());
      } catch (Exception e) {
        System.out.printf("%s 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) throws Exception {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    executorService.execute(new MyRunnable(6000));
    executorService.execute(new MyRunnable(2000));
    executorService.execute(new MyRunnable(4000));
    executorService.execute(new MyRunnable(20000));

    // 실행 중인 작업 및 대기 중인 작업이 모두 끝나면 스레드풀을 종료하라!
    executorService.shutdown();

    // 스레드풀의 모든 스레드가 종료될 때까지 기다린다.
    if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
      System.out.println("아직 종료 안된 작업이 있다.");
      System.out.println("남아 있는 작업의 강제 종료를 시도하겠다.");

      // => 만약 10초가 경과될 때까지 종료되지 않으면,
      //    대기 중인 작업을 강제 종료하라고 지시한다.

      // 강제 종료?
      // => 일단 대기 중인 작업은 모두 취소한다.
      // => 실행 중인 스레드 중 Not Runnable 상태에 있는 스레드는 강제 종료한다.
      // => 그 외 running 상태의 스레드는 강제 종료할 수 없다.
      //    예) 입.출력 대기 상태는 running 상태이다. Not Runnable 상태가 아니다.
      executorService.shutdownNow();

      // 그리고 다시 작업이 종료될 때까지 기다린다.
      if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
        System.out.println("스레드풀의 강제 종료를 완료하지 못했다.");
      } else {
        System.out.println("모든 작업을 강제 종료했다.");
      }

    }

    System.out.println("main() 종료!");
  }
}

ㄴ 만약 10초가 경과될 때까지 종료되지 않으면, 대기 중인 작업을 강제 종료하라고 지시함

ㄴ  강제 종료?
       => 일단 대기 중인 작업은 모두 취소함
       => 실행 중인 스레드 중 Not Runnable 상태에 있는 스레드는 강제 종료함
       => 그 외 running 상태의 스레드는 강제 종료할 수 없음
          예) 입.출력 대기 상태는 running 상태이다. Not Runnable 상태가 아님

=>