
앞서 동기화와 wait()/notift()/notifyAll() 에 대해서 알아보았다.
이번에는 남은 스레드 제어 메서드에 대해서 정리하고자 한다.
지정된 시간동안 스레드를 재우고 ( 일시정지 시킨다 ) 지정한 시간이 지나고 나면 자동적으로 다시 RUNNABLE 상태가 된다.
static void sleep(long millis) 로 특정 스레드를 지정하여 재울 수는 없고 오로지 자기 자신에게만 호출이 가능하다.
Thread.sleep( ) 로 사용을 한다.
sleep( ) 은 사용시 잠들 시간계산을 정확히 해줘야 하는 번거로움이 있다.
만약 너무 짧게 잡으면 원하는 내용이 전부 수행되지 못할 수도 있고
너무 길게 잡으면 낭비되는 시간이 많아진다.
항상 InterruptedException 를 예외처리 해줘야 한다.
누군가 자고 있는 스레드를 interrupt( ) 로 깨우면 throw new InterruptedException 로 인해 예외가 발생하고 깨어난다.
깨우는 것이 목적이라 catch 문에 따로 예외 발생시 내용이 없어도 문제는 없다.
다른 스레드에게 제어권을 양보한다. 이때 양보는 우리가 아는 양보와 달리 양보할 기회를 주는 것이다.
이 때 빠르게 양보 기회를 받아야 다른 스레드가 작동하고 버벅거리면 다시 기존 스레드가 작업을 진행하게 된다.
그래서 기존 스레드가 버벅거려 다른 스레드에게 기회를 넘겨줘 과부화가 걸리지 않는 보조 역할을 수행할 때 주로 사용한다.
static void yield( ) 로 특정 스레드를 지정하여 양보시킬 순 없고 오로지 자기 자신에게만 호출이 가능하다.
Thread.yield( ) 로 사용을 한다.
지정된 시간동안 특정 스레드가 작업하는 것을 기다리게 blocking 을 걸어준다.
가령 스레드A.join( ) 메서드를 호출하면 다른 작업들은 스레드A가 끝날 때까지 block에 의해 다음 작업으로 진행하지 못한다.
이렇게 막고 있다가 끝나야 지나가게 해주는 blocking 방식을 동기 방식이라고 한다.
앞선 yield, sleep 과 달리 특정 메서드 지정이 가능하다.
void join( ) 로 특정 시간이 없으면 작업이 모두 끝날 때까지 기다린다.
join( ) 도 sleep( ) 과 동일하게 interrupt( ) 에 의해 깨어나기 때문에 예외 처리가 필수다.
기본적으로 thread 는 run( ) 의 실행 내용이 모두 실행되면 종료된다.
그 중 while(true) 와 같은 무한 반복문으로 이루어진 경우는 강제 종료가 필요하다.
이때 stop( ) 메서드를 사용할 수는 있으나 현재 @deprecated 로 사용중지를 권고하고 있다.
stop( ) 은 즉시 종료로 급작스럽게 종료되기 때문에 문제가 발생할 여지가 많다.


스레드 안에 무한 반복문에 stop == false 와 같은 조건을 걸어 stop 값이 true/false 에 따라 반복문에서 벗어나 스레드의 내용을 종료할 수 있도록 한다.
이러한 코드 실행을 stop flag 라고 하고 멈추기 위한 값은 플래그 값이라고 부른다.


interrupt( ) 는 강제로 Exception 을 발생하여 반복문을 빠져나오게 한다.
stop flag 는 플래그 값이 안바뀌는 경우가 생길 수 있는 경우가 있지만 interrupt( ) 은 무조건 예외 발생으로 좀 더 강력하다.
하지만 sleep 이 강제적으로 1m/s 라도 발생해야 하고 강제 예외 발생이라 좋은 방법이라 하긴 애매하다.
이 때 interrupted( ) 를 사용하여 interrupt 가 발생했는지 확인하여 true 이면 반복문을 빠져나가는 식으로 정지시킬 수 있다.
static boolean interrupted( )
: Thread.interrupted( ) 로 사용이 가능하며 현재 스레드의 interrupted 상태를 반환해주고 false 로 초기화한다.
( false 로 초기화해줘야 다시 interrupt( ) 할 때 false->true 로 전환이 되어 interrupted 상태 확인이 가능하다 )

데몬 스레드는 사용자 스레드 ( 여기선 main Thread ) 작업을 돕는 보조적인 역할을 주로 수행한다.
그래서 메인 스레드의 워크 스레드와 달리 메인 스레드가 종료되면 자동적으로 종료가 된다.
가비지 컬렉터 ( CG ) , 자동저장 , 화면자동갱신 등에 사용한다.
boolean isDaemon( ) : 데몬스레드인지 확인하고 true/false 반환
void setDaemon(boolean on)
: true 를 지정하면 특정 스레드를 데몬스레드로 만들 수 있다.
setDaemon 은 반드시 start( ) 호출 전에 만들어줘야 한다.
start( ) 호출 후 설정해주면 IllegalThreadStateException 이 발생한다.


스레드는 한 번 생성할 때마다 OS 가 해당 스레드를 위한 메모리 영역을 확보해주고 스레드가 필요 없을 땐 다시 이 메모리 영역을 회수하는 작업이 일어난다. 이는 비용이 상당한 작업이기 때문에 이런 상황이 반복될 경우 퍼포먼스에 영향이 간다.
이를 제어하기 위해 Thread Pool 이 존재한다.
Thread Pool 은 대여소와 같은 역할을 수행한다.
Thread 를 보유하고 있다가 순서에 따라 빌려주고, 사용 후 돌려받는다.

Executors.newCachedThreadPool() 로 생성한 Thread pool 안에는 처음에 스레드가 존재하지 않는다,.
요청이 있으면 그때부터 스레드풀에 스레드를 만든다.
요청이 있을때 반납받은 스레드가 있으면 그 스레드를 빌려준다.
반납받은 스레드가 60 초 이상 빌리는 사람이 없으면 폐기한다.



execute( ) 는 void 메서드로 반환값이 없어 작업 처리 결과를 받지 못한다.
따라서 작업 처리 도중 예외상황이 발생하면 스레드를 종료하고 풀에서 스레드를 제거한다.
Runnable 로 작업한 객체만 들어갈 수 있다.
submit( ) 은 Future 타입의 값을 반환하여 작업 처리 결과를 얻을 수 있다.
따라서 작업 처리 도중 예외상황이 발생해도 스레드를 종료하지 않고 재사용한다.
Runnable, Callable 로 작업한 객체 다 들어갈 수 있다.
Callable 의 반환값은 Future의 메서드 get() 을 통해 값을 얻을 수 있다.
이때 get( ) 은 앞서 배운 join( ) 과 같이 blocking 이 가능하다.

Thread pool 에서는 join( ) 을 사용하지 못한다.
대신에 blocking 이 가능한 get( ) 이 그 역할을 대신 해 줄 수 있다.
여기서 get( ) 메서드가 없으면 System.out.println(sum) 보다 System.out.println("1~100까지의 합은?") 이 먼저 출력된다.
블록으로 막지 않으니 먼저 빠르게 처리가 가능한 것부터 실행하는 것이다.
하지만 get( ) 메서드가 있으면 sum 합계를 구하고 출력까지 기달려줘서 출력값이 반대로 나오게 된다.
Callable 로 만든 작업은 submit( ) 메서드로만 작업요청이 가능해 Future 의 get( ) 메서드를 쉽게 얻을 수 있다.
Runnable 은 그럼 어떻게 blocking을 할까?
