본문으로 바로가기

<자바>

메인(main)스레드
: 모든 자바 프로그램은 메인 스레드가 main() 메소드를 실행하면서 시작된다.
: 코드의 실행 흐름 -> 스레드
: main 메소드에서 return 문을 만나면 실행이 종료된다,
: JVM이 메인 스레드를 만듬

: main스레드는 작업 스레드들을 만들어서 병렬로 코드를 실행할수 있다.
=> 멀티 스레드를 생성해서 멀티 태스킹을 수행한다.

프로세스의 종료
싱글 스레드; 메인 스레드가 종료하면 프로세스도 종료된다.
멀티 스레드 : 실행중인 스레드가 하나라도 있다면, 프로세스는 종료되지 X
=> 메인 스레드가 작업 스레드보다 먼저 종료되더라도 작업 스레드가 계속 실행중이라면
프로세스는 종료되지 않음
=> 멀티스레드일 경우 모든 스레드가 종료 되어야지 멈춤



스레드 풀

스레드 폭증
1: 병렬 작업 처리가 많아지면 스레드의 개수가 증가
2. 스레드 생성과 스케쥴링으로 인해 CPU가 바빠지고 메모리 사용량이 늘어남
3. 어플리케이션의 성능이 급격히 저하됨

스레드 풀(ExecutorService)
: 작업 처리에 사용되는 스레드를 제한된 개수만큼 미리 생성
: 작업 큐에 들어오는 작업들을 하나씩 스레드가 맡아서 처리
: 작업 처리가 끝난 스레드는 작업 결과를 애플리케이션을 전달
: 스레드는 다시 작업큐에서 새로운 작업을 가져와 처리
ExecutorService 인터페이스와 Executors 클래스
: 스레드풀을 생성하고 사용할수 있도록 java.util.concurrent 패키지에서 제공
: Executors의 정적 메소드를 이용해서 ExecutorService 구현 객체 생성
: 스레드 풀 = ExecutorService 객체



스레드풀 생성

다음 두가지 메소드 중 하나로 간편 생성

코어 스레드수
: 작업량이 많아지게 되면 스레드가 늘어나다가 작업량이 줄어들게 되면 놀게 되는 스레드가 발생하게 된다.
  코어 스레드는 놀게 되는 스레드가 있을경우 즉, 사용하지 않았을때도 유지되어야 하는 스레드를 말한다. 

  • newCachedThreadPool()
    • int값이 가질수 있는 최대 값만큼 스레드가 추가되나, 운영체제의 상황에 따라 달라진다.
    • 1개 이상의 스레드가 추가되었을 경우
    • 60초 동안 추가된 스레드가 아무 작업을 하지 않으면
    • 추가된 스레드를 종료하고 풀에서 제거한다.
      • ExecutorService executorService = Executors.newCachedThreadPool();
  • new FixedThreadPool(int nThreads)
    • 코어 스레드 개수와 최대 스레드 개수가 매개값으로 준 nThread이다.
    • 스레드가 작업을 처리하지 않고 놀고 있더라도 스레드 개수가 줄지 않는다.



ThreadPoolExecutor을 이용한 직접 생성
: newCachedThreadPool()와 newFixedThreadPool(int nThreads)가 내부적으로 생성
: 스레드의 수를 자동으로 관리하고 싶을 경우 직접 생성해서 사용


스레드풀 종료
  • 스레드풀의 스레드는 기본적으로 데몬 스레드가 아니다
    • main 스레드가 종료되더라도 스레드풀의 스레드는 작업을 처리하기 위해 계속 실행되므로 애플리케이션은 종료되지 않는다.
    • 따라서 스레드풀을 종료해서 모든 스레드를 종료시켜야 한다.
  • 스레드풀 종료 메소드
    • void shutdown() 
      • 현재 처리 중인 작업뿐만 아니라 작업큐에 대기하고 있는 모든 작업을 처리한 뒤에 스레드풀을 종료시킨다.
    • List<Runnable> shutdownNow()
      • 현재 작업 처리 중인 스레드를 interrupt 해서 작업중지를 시도하고 스레드풀을 종료시킨다.
      • 리턴값은 작업큐에 미처리된 작업(Runnable)의 목록이다.
      • 사용하는게 좋은 방법은 아니다.

    • boolean awaitTermination(long timeout,TimeUnit unit)
      • shutdown() 메소드 호출 이후, 모든 작업 처리를 timeout 시간 내에 완료하면 true를 리턴하고, 완료하지 못하면
      • 작업 처리 중인 스레드를 interrupt 하고 false를 리턴한다.


작업 생성
: 하나의 작업은 Runnable 또는 Callable 객체로 표현한다.
: Runable과 Callable의 차이점
  • 작업 처리 완료 후 리턴값이 있느냐 없느냐 이다.

Runnable 구현 클래스
Callable 구현 클래스
Runnable task = new Runnable(){
  @Override
  public void run(){
      스레드가 처리할 작업 내용
  }

}



Callable<T> task = new Callable<T> {
   @Override
   public T call() throws Exception{
   //스레드가 처리할 작업 내용
    return T;
   }
}

: 스레드풀에서 작업 처리
  • 작업큐에서 Runnable 또는 Callable 객체를 가져와 스레드로 하여금 run()과 call()메소드를 실행토록 하는 것이다.

작업처리 요청
  • ExecutorService의 작업 큐에 Runnable 또는 Callable 객체를 넣는 행위를 말한다.
  • 작업 처리 요청을 위해 ExecutorService는 다음 두 가지 종류의 메소드를 제공한다.


리턴타입
메소드명(매개변수)
설명
void
execute(Runnable command)
  • Runnable 을 작업큐에 저장
  • 작업 처리 결과를 받지 못함
Future<?>
Future<V>
Future<V>
submit(Runnable task)
submit(Runnable task, V result)
submit(Callable <V> task)
  • Runnable 또는 Callable 을 작업큐에 저장
  • 리턴된 Future를 통해 작업 처리 결과 얻을수 있음

  • 작업 처리 도중 예외가 발생할 경우
    • execute()
      • 스레드가 종료되고 해당 스레드는 제거된다.
      • 따라서 스레드풀은 다른 작업 처리를 위해 새로운 스레드를 생성한다.
    • submit()
      • 스레드가 종료되지 않고 다음 작업을 위해 재사용된다.

                    => submit이 더 좋다.
                    => 리턴값이 없는것도 submit을 쓰는게 더 좋다.

블로킹 방식의 작업 완료 통보 받기

리턴타입
메소드명(매개변수)
설명
Future<?>
submit(Runnable task)
  • Runnable 또는 Callable을 작업큐에 저장
  • 리턴된 Future를 통해 작업 처리 결과 얻음
Future<V>
submit(Runnable task, V result)
Future<V>
submit(Callable<V> task)

  •  Future
    • 작업 결과가 아니라 지연 완료(pending completion) 객체
    • 작업이 완료될까지 기다렸다가 최종 결과를 얻기 위해서 get() 메소드 사용

리턴타입
메소드명(매개변수)
설명
V
get()
작업이 완료될 때까지 블로킹되었다가 처리 결과 V를 리턴 , 작업이 완료될때 까지 기다리겠다는 의미 (Blocking)
V
get(long timeout, TimeUnit unit)
timeout 시간동안 작업이 완료되면 결과 V를 리턴하지만 , 작업이 완료되지 않으면 TimeoutException을 발생시킴

메소드
작업 처리 완료후 리턴 타입
작업처리 도중 예외 발생
submit(Runnable task)
future.get() ->null
future.get() -> 예외 발생
submit(Runnable task,Integer result)
future.get() -> int 타입 값
future.get() -> 예외 발생
submit(Callable<String> task)
future.get() -> String 타입 값
future.get() -> 예외 발생

  • Future의 get()은 UI 스레드에서 호출하면 안된다.
    • UI를 변경하고 이벤트를 처리하는 스레드가 get() 메소드를 호출하면
    • 작업을 완료하기 전까지는 UI를 변경할 수도 없고 이벤트도 처리할 수 없게 된다.
  • 작업 처리 결과를 외부 객체에 저장

Result result = "";
Runnable task = new Task(result);
Future<Result> future = executorService.submit(task,result);
result = future.get();




class Task implements Runnable{
     Result result;
     Task(Result result) { this.result = result;}
     @Override
     public void run(){
        작업코드
         처리 결과를 result에 저장      
      }
}


작업 완료순으로 통보 받기
  • 작업 요청 순서대로 작업 처리가 완료되는 것은 아니다.
    • 작업의 양과 스레드 스케쥴링에 따라서 먼저 요청한 작업이 나중에 완료되는 경우도 발생
    • 여러 개의 작업들이 순차적으로 처리될 필요성이 없고,
    • 처리 결과도 순차적으로 이용할 필요가 없다면
    • 작업 처리가 완료된 것부터 결과를 얻어 이용하는 것이 좋다.
  • 스레드풀에서 작업 처리가 완료된 것만 통보 받는 방법
    • CompletionService는 처리 완료된 작업을 가져오는 poll()과 take() 메소드를 제공한다.

리턴타입
메소드명
설명
Future<V>
poll()
완료된 작업의 Future를 가져옴
완료된 작업이 없다면 즉시 null을 리턴함
Future<V>
poll(long timeout,TimeUnit unit)
완료된 작업의 Future를 가져옴
완료된 작업이 없다면 timeout까지 블로킹됨
Future<V>
take()
완료된 작업의 Future를 가져옴
완료된 작업이 없다면 있을 때까지 블로킹됨
Future<V>
submit(Callable<V> task)
스레드풀에 Callable 작업 처리 요청
Future<V>
submit(Runnable task, V result)
스레드풀에 Runnable 작업 처리 요청-


  • CompletionService 객체 얻기
ExecutorService executorService = Executors.newFixedThreadPool(2);
CompletionService<V> completionService = new ExecutorCompletionService<V>(executorService);

  • 작업 처리 요청 방법
    • poll()과 take() 메소드를 이용해서 처리 완료된 작업의 Future을 얻으려면
    • CompletionService의 submit() 메소드로 작업 처리 요청을 해야한다.


콜백 방식의 작업 완료 통보 받기
  • 콜백이란?
    • 애플리케이션이 스레드에게 작업 처리를 요청한 후 , 다른 기능을 수행할 동안,
    • 스레드가 작업을 완료하면 애플리케이션의 메소드를 자동 실행하는 기업을 말함
    • 이 때 자동 실행되는 메소드를 콜백 메소드라고 한다.
  • 작업 완료 통보 얻기: 블로킹 vs 콜백 


  • 콜백 객체와 콜백 하기
    • 콜백 객체: 콜백 메소드를 가지고 있는 객체
                   : java.nio.channels.CompletionHandler 인터페이스 활용해서 만들수도 있다.