TIL / JAVA 5주차(5) / Thread 제어 메서드 / 데몬스레드 / Thread Pool

병아리코더 아카이브·2023년 9월 13일

JAVA

목록 보기
19/20
post-thumbnail

Thread 제어

앞서 동기화와 wait()/notift()/notifyAll() 에 대해서 알아보았다.
이번에는 남은 스레드 제어 메서드에 대해서 정리하고자 한다.


sleep( )

  • 지정된 시간동안 스레드를 재우고 ( 일시정지 시킨다 ) 지정한 시간이 지나고 나면 자동적으로 다시 RUNNABLE 상태가 된다.

  • static void sleep(long millis) 로 특정 스레드를 지정하여 재울 수는 없고 오로지 자기 자신에게만 호출이 가능하다.

  • Thread.sleep( ) 로 사용을 한다.

  • sleep( ) 은 사용시 잠들 시간계산을 정확히 해줘야 하는 번거로움이 있다.
    만약 너무 짧게 잡으면 원하는 내용이 전부 수행되지 못할 수도 있고
    너무 길게 잡으면 낭비되는 시간이 많아진다.

  • 항상 InterruptedException 를 예외처리 해줘야 한다.

누군가 자고 있는 스레드를 interrupt( ) 로 깨우면 throw new InterruptedException 로 인해 예외가 발생하고 깨어난다.
깨우는 것이 목적이라 catch 문에 따로 예외 발생시 내용이 없어도 문제는 없다.


yield( )

  • 다른 스레드에게 제어권을 양보한다. 이때 양보는 우리가 아는 양보와 달리 양보할 기회를 주는 것이다.

  • 이 때 빠르게 양보 기회를 받아야 다른 스레드가 작동하고 버벅거리면 다시 기존 스레드가 작업을 진행하게 된다.

  • 그래서 기존 스레드가 버벅거려 다른 스레드에게 기회를 넘겨줘 과부화가 걸리지 않는 보조 역할을 수행할 때 주로 사용한다.

  • static void yield( ) 로 특정 스레드를 지정하여 양보시킬 순 없고 오로지 자기 자신에게만 호출이 가능하다.

  • Thread.yield( ) 로 사용을 한다.


join( )

  • 지정된 시간동안 특정 스레드가 작업하는 것을 기다리게 blocking 을 걸어준다.

  • 가령 스레드A.join( ) 메서드를 호출하면 다른 작업들은 스레드A가 끝날 때까지 block에 의해 다음 작업으로 진행하지 못한다.

이렇게 막고 있다가 끝나야 지나가게 해주는 blocking 방식을 동기 방식이라고 한다.

  • 앞선 yield, sleep 과 달리 특정 메서드 지정이 가능하다.

  • void join( ) 로 특정 시간이 없으면 작업이 모두 끝날 때까지 기다린다.

  • join( ) 도 sleep( ) 과 동일하게 interrupt( ) 에 의해 깨어나기 때문에 예외 처리가 필수다.


Stop flag / interrupt( )

  • 기본적으로 thread 는 run( ) 의 실행 내용이 모두 실행되면 종료된다.

  • 그 중 while(true) 와 같은 무한 반복문으로 이루어진 경우는 강제 종료가 필요하다.

  • 이때 stop( ) 메서드를 사용할 수는 있으나 현재 @deprecated 로 사용중지를 권고하고 있다.

stop( ) 은 즉시 종료로 급작스럽게 종료되기 때문에 문제가 발생할 여지가 많다.

stop flag 를 이용한 정지방법

  • 스레드 안에 무한 반복문에 stop == false 와 같은 조건을 걸어 stop 값이 true/false 에 따라 반복문에서 벗어나 스레드의 내용을 종료할 수 있도록 한다.

  • 이러한 코드 실행을 stop flag 라고 하고 멈추기 위한 값은 플래그 값이라고 부른다.

interrupt 를 이용한 정지방법

  • interrupt( ) 는 강제로 Exception 을 발생하여 반복문을 빠져나오게 한다.

  • stop flag 는 플래그 값이 안바뀌는 경우가 생길 수 있는 경우가 있지만 interrupt( ) 은 무조건 예외 발생으로 좀 더 강력하다.

  • 하지만 sleep 이 강제적으로 1m/s 라도 발생해야 하고 강제 예외 발생이라 좋은 방법이라 하긴 애매하다.

  • 이 때 interrupted( ) 를 사용하여 interrupt 가 발생했는지 확인하여 true 이면 반복문을 빠져나가는 식으로 정지시킬 수 있다.

static boolean interrupted( )
: Thread.interrupted( ) 로 사용이 가능하며 현재 스레드의 interrupted 상태를 반환해주고 false 로 초기화한다.
( false 로 초기화해줘야 다시 interrupt( ) 할 때 false->true 로 전환이 되어 interrupted 상태 확인이 가능하다 )



Daemon Thread

  • 데몬 스레드는 사용자 스레드 ( 여기선 main Thread ) 작업을 돕는 보조적인 역할을 주로 수행한다.

  • 그래서 메인 스레드의 워크 스레드와 달리 메인 스레드가 종료되면 자동적으로 종료가 된다.

  • 가비지 컬렉터 ( CG ) , 자동저장 , 화면자동갱신 등에 사용한다.

데몬스레드 관련 메서드

  • boolean isDaemon( ) : 데몬스레드인지 확인하고 true/false 반환

  • void setDaemon(boolean on)
    : true 를 지정하면 특정 스레드를 데몬스레드로 만들 수 있다.

setDaemon 은 반드시 start( ) 호출 전에 만들어줘야 한다.
start( ) 호출 후 설정해주면 IllegalThreadStateException 이 발생한다.

데몬스레드 사용 방법

  • 데몬 스레드로 만들 WorkThread 클래스를 만든다.
  • 데몬 스레드는 자동저장, 자동갱신 등에 사용되어 주로 무한 반복으로 상시 대기하며 특정 조건이 만족하면 작업을 수행하고 다시 대기하도록 작성된다.
  • 중간에 Thread.sleep( ) 으로 쉬는 시간을 주기도 한다.
  • 일반 스레드가 종료하면 같이 종료가 되기 때문에 무한루프로 진행해도 괜찮다.


Thread Pool

  • 스레드는 한 번 생성할 때마다 OS 가 해당 스레드를 위한 메모리 영역을 확보해주고 스레드가 필요 없을 땐 다시 이 메모리 영역을 회수하는 작업이 일어난다. 이는 비용이 상당한 작업이기 때문에 이런 상황이 반복될 경우 퍼포먼스에 영향이 간다.
    이를 제어하기 위해 Thread Pool 이 존재한다.

  • Thread Pool 은 대여소와 같은 역할을 수행한다.
    Thread 를 보유하고 있다가 순서에 따라 빌려주고, 사용 후 돌려받는다.


Thread Pool 생성

  • Thread Pool 은 ExecutorService 객체를 통해 생성 된다

newCachedThreadPool()

  • Executors.newCachedThreadPool() 로 생성한 Thread pool 안에는 처음에 스레드가 존재하지 않는다,.

  • 요청이 있으면 그때부터 스레드풀에 스레드를 만든다.

  • 요청이 있을때 반납받은 스레드가 있으면 그 스레드를 빌려준다.

  • 반납받은 스레드가 60 초 이상 빌리는 사람이 없으면 폐기한다.

newFixedThreadPool(n)

  • Executors.newFixedThreadPool(숫자) 로 생성한 Thread pool 안에는 숫자만큼의 스레드 수를 미리 만들어 놓고 유지한다.

Runnable / Callable

  • Thread pool 로 대여받아서 할 작업을 적는 건 Runnable, Callable 로 통해 만든다.

1. Runnable

  • Runnable 은 반환할 것이 없을 때 사용한다.
    여기서 Runnable 익명객체의 run( ) 메서드는 void 로 반환할 값이 없다.

2. Callable

  • Callable 은 반환할 것이 있으면 사용한다.
  • 여기서 Callable 익명객체의 call( ) 메서드는 String 으로 문자열을 반환하고 이걸 제네릭 타입으로 받는다.

execute( ) / submit( )

  • 생성된 작업은 생성한 thread pool 의 execute( ) / submit( ) 메서드의 매개변수로 넣어 실행을 요청한다.

execute

  • execute( ) 는 void 메서드로 반환값이 없어 작업 처리 결과를 받지 못한다.

  • 따라서 작업 처리 도중 예외상황이 발생하면 스레드를 종료하고 풀에서 스레드를 제거한다.

  • Runnable 로 작업한 객체만 들어갈 수 있다.

submit

  • submit( ) 은 Future 타입의 값을 반환하여 작업 처리 결과를 얻을 수 있다.

  • 따라서 작업 처리 도중 예외상황이 발생해도 스레드를 종료하지 않고 재사용한다.

  • Runnable, Callable 로 작업한 객체 다 들어갈 수 있다.

  • Callable 의 반환값은 Future의 메서드 get() 을 통해 값을 얻을 수 있다.

이때 get( ) 은 앞서 배운 join( ) 과 같이 blocking 이 가능하다.

get

  • Thread pool 에서는 join( ) 을 사용하지 못한다.
    대신에 blocking 이 가능한 get( ) 이 그 역할을 대신 해 줄 수 있다.

  • 여기서 get( ) 메서드가 없으면 System.out.println(sum) 보다 System.out.println("1~100까지의 합은?") 이 먼저 출력된다.
    블록으로 막지 않으니 먼저 빠르게 처리가 가능한 것부터 실행하는 것이다.

  • 하지만 get( ) 메서드가 있으면 sum 합계를 구하고 출력까지 기달려줘서 출력값이 반대로 나오게 된다.

  • Callable 로 만든 작업은 submit( ) 메서드로만 작업요청이 가능해 Future 의 get( ) 메서드를 쉽게 얻을 수 있다.

  • Runnable 은 그럼 어떻게 blocking을 할까?

  1. Callable 로 바꿔주고 의미없는 return 0 값을 반환시킨다.
  2. submit( ) 으로 작업요청한다.
  • execute( ) 가 Runnable 사용만 가능한 것이지 Runnable 은 반환값이 없어도 submit( ) 도 사용이 가능하기 때문에 응용하여 get( ) 으로 blocking 을 할 수 있다.

  • 이때 Runnable 로 submit 작업 요청한 값을 get( ) 메서드로 찍으면 null 이 나온다.

0개의 댓글