🏃♂️ 들어가기 앞서..
본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 Page : 〔투 비 마스터 : 자바〕
*해당 교재의 목차 순서와 구성을 참고하여 작성하며
각 내용마다 부족할 수 있는 내용이나 개인적으로 궁금한 점은
추가적인 검색을 통해 채워나갈 예정입니다.
【 쓰레드 생성 ~ 종료 】의 과정 동안
쓰레드는 여러 가지 상태를 가지게 되는데
상태의 종류는 다음과 같다.
NEW : 생성은 되었지만 아직 start() 호출이 안된 상태
RUNNABLE : 실행 중 or 실행 가능한 상태
→ 실행 뿐만 아니라 실행 후에 다시 자신 차례가 올 때까지 실행 대기를 하는 상태 또한 RUNNABLE 상태라고 한다.
BLOCKED : 동기화 블럭에 의해 일시 정지된 상태
( 풀릴 때까지 기다리는 상태 )
WAITING / TIMED_WAITING : 종료는 아니지만 실행 불가능한 일시 정지 상태
( TIMED_WAITING 은 시간이 지정된 일시정지 )
TERMINATED : 작업이 종료된 상태 → 소멸
아래 실행 제어에서 자세하게 알아보겠지만
각 상태 변환에 사용되는 메서드/기능은 간략하게 다음과 같다.ⓐ NEW → RUNNABLE :
start()
ⓑ RUNNABLE → TERMINATED :
stop()
ⓒ RUNNABLE → BLOCKED / WAITING :
suspend()
,sleep()
,wait()
,join()
, " I/O block " 구간ⓓ BLOCKED / WAITING → RUNNABLE : " time-out ",
resume()
,notify()
,interrupt()
ⓔ < 둘다 RUNNABLE 상태 > 실행 → 실행 대기 :
yield()
쓰레드 프로그래밍의 핵심은 " 동기화(Synchronization) "와 " 스케쥴링(Scheduling) " 이다.
우선순위를 활용한 정교한 스케쥴링으로
주어진 자원과 시간을 낭비없이 잘 사용하는 것이 중요하다.
우선 위에서 잠깐 알아봤던 메서드들을 지금부터 알아보자.
Method | 설명 |
---|---|
static void sleep ( long millis ) static void sleep ( long millis, int nanos ) | 지정된 시간 (milli second 단위) 동안 쓰레드 일시 정지 시간지나면 자동적으로 다시 실행 대기 상태 전환 |
void join() void join( long millis ) void join( long millis, int nanos ) | 지정된 시간 (milli second 단위) 동안 쓰레드 독점실행 " 시간 경과 " or " 작업 종료 " ▶ join() 을 호출했던 쓰레드로 귀환 & 실행 |
void interrupt() | " 깨우기 " sleep() 이나 join() 으로 일시정지 상태인 쓰레드를 실행 대기 상태로 변환( 해당 쓰레드 : InterruptedException 발생 → 일시정지상태 탈출 ) |
void stop() | " 즉시 종료 " |
void suspend() | " 일시 정지 " |
void resume() | " 재개 " |
static void yield() | 자신의 차례를 다른 쓰레드에게 " 양보 " & 실행 대기 상태로 다시 대기 |
* 유일하게 static 메서드인 sleep()
과 yield()
: 쓰레드 " 자기 자신 "에게만 호출이 가능하다.
→ 자거나 양보하는건 자기 스스로에게만 적용되는 것
* 단, resume()
, stop()
, suspend()
는 deprecated 되어있다. ▶ 교착상태(dead-lock) 발생 위험도 ↑
sleep()
: 일시 정지 ( static ): 《현재 쓰레드》를 지정된 시간동안 쓰레드를 일시 정지 _ static 메서드
* 독특하게도 스스로 예외를 발생시켜 깨어나는 유형
static void sleep( long millis )
static void sleep( long millis, int nanos )
지정 시간의 단위는 "밀리세컨드 (1/1000 초)" & "나노세컨드(1/10억 초)" 가 있다.
ex.
/* 쓰레드 0.0015초동안 멈추게 하기 */
try {
Thread.sleep(1, 500000) ; // (1밀리 세컨드 : 1000000 나노 세컨드 초 + 500000 ) / 1000000000
// == 1500000 / 1000000000 = 15/10000 = 0.0015초
} catch (InterruptedException e) {}
// 해당 예외가 발생하면 자동 기상
위와 같은 방법으로 sleep()
메서드를 사용하면 되는데
주의해야 할 점이 하나 있다.
위에서도 볼 수 있다시피
sleep()
메서드는
예외가 발생 throw InterruptedException
하기때문에
꼭 예외처리를 해줘야한다.
(매번 처리하기 귀찮기 때문에 하나의 별도 메서드를 만들어 예외처리까지 포함한 실행문을 사용하는 것이 좋다.)
또 하나 주의할 점은
static 메서드라는 점이다.
일시정지 시키는 명령은
자기 자신 에게만 가능하고
특정 쓰레드를 지정해서 멈추게 할 순 없다.
/* main 쓰레드 그룹에 소속된 th1 쓰레드(특정 쓰레드) */
...
try {
th1.sleep(2000) ; // 오류는 발생하지 않음
} catch (InterruptedException e) {}
/* 정작 자고 있는건 "" main 쓰레드 ""이다 */
// 바람직한 것은 Thread.sleep()
try {
Thread.sleep(2000) ; // 오류는 발생하지 않음
} catch (InterruptedException e) {}
특정 쓰레드 매개변수에 sleep()
메서드를 실행시켰을 때,
"오류가 발생하지는 않아서 " 오해가 생길 수도 있지만
하지만
실제로 멈추는 것은 특정 쓰레드가 아닌
main 메서드를 실행하고 있는 main 쓰레드가 멈추게 된다.
마지막으로
이 sleep()
이 실행되어 일시정지된 쓰레드가
다시 실행되는 경우는 2가지다.
interrupt()
: 강제 깨우기interrupt()
: 정지 해제(활성화): 대기 상태(WAITING)인 쓰레드를 " 실행대기 (RUNNABLE) " 상태로 활성화
대기 상태 중에는
이미 한 쓰레드가 어떠한 작업을 하고 있는 동안 (실행되는 동안) 잠깐 멈춰있는 상태도 포함된다.
즉,
" 특정 작업이 수행되고 있던 것을 종료시키고 작업 순환이 돌아가게 하는 것 "도 실행대기 상태로 정지 해제시키는 것이라고 할 수 있다는 점 알아두자.
void interrupt() // 쓰레드 interrupted 상태 ( 인스턴스 변수 interrupted ) 변환 : false → true
boolean isInterrupted() // 쓰레드의 interrupted 상태 반환 << 확인용 >>
/*
!! 주의 !!
interrupt실행 (true) → true 반환 & interrupted 안된 상태 (false)로 초기화
interrupt 미실행 (false) → false 반환 & false 상태 유지
*/
static boolean interrupted() // <static메서드> "현재 쓰레드" interrupted 상태 "반환" & false로 "초기화"
이 interrupt()
는 쓰레드의 interrupted 상태 (인스턴스 변수 interrupted
)를
interrupt()
가 실행되었다고 true로 바꾸는 것일 뿐이다.
해당 실행 관계를 통해서
[ while문 ]에서 사용될 때
!isInterripted
나 !interrupted
가 조건식으로 사용된다.
둘의 차이점은
아무래도 static 여부에 있다.
간단하게 main 쓰레드를 기준으로 보자면
main 쓰레드에서 interrupted 상태 여부에 따른 while문 조건식에는 !interrupted
가 쓰일 수 있겠고
main 쓰레드에서 생성한 소속 쓰레드들의 while문 조건식에는 !isInterripted
가 쓰일 것이다.
public void run() {
...
while(!isInterrupted()) {
// interrupt()실행 전 or 작업 모두 수행할 때 까지 "묵묵히 실행할 작업"
...
}
System.out.println("작업 완료");
}
join()
: 일시 정지 ( non-static ): 지정 시간동안 특정 쓰레드가 작업하는 것을 기다리게 한다.
《 작업중 특정 쓰레드의 작업이 먼저 수행되어야 할 때 》
시간을 지정하지 않으면
해당 쓰레드가 작업을 모두 마칠 때까지 기다리게 된다.
( ex. main 쓰레드에서 소속 쓰레드 th1 쓰레드에 join이 호출
→ main 쓰레드의 작업은 잠깐 정지 & th1 쓰레드 작업 끝날 때까지 main쓰레드 정지 )
// 매개변수 구성이 sleep()과 동일
void join()
void join( long millis )
void join( long millis, int nanos )
구성도 sleep()
과 동일하듯이
사용하는 방법 또한 동일하다.
" interrupt()
에 의해 대기상태 탈출 "하는 특징과
" 예외처리 "를 해야한다는 특징이 있다.
다른 점이 있다면
이 join()
메서드는 static 메서드가 아니기 때문에
특정 쓰레드에 대해 동작한다.
yield()
: 순서 양보: 쓰레드 자신에게 주어진 실행시간을 다음 차례 쓰레드에게 양보
★ static 메서드 ★( ex. OS 스케줄러에 의해 1초의 시간을 할당 받았다고 할 때, 0.5초 작업한 시점에
yield()
가 호출되면 " 나머지 0.5초를 포기 "하고 실행 대기열에 다시 대기한다. )
이 【 yield()
메서드 + interrupt()
메서드 】를 잘 활용하면
불필요한 " 무의미한 무한 루프 대기로 인한 특정 쓰레드 독점 " 을 방지할 수 있는 것과 더불어
프로그램의 응답성 증진과
효율적인 실행 제고가 가능하다.
하지만 yield()
또한
희망 사항을 스케쥴러에 전달하는 것일 뿐이고
OS 스케줄러가 전체 프로그램들의 작업 효율을 따져 시간 배분을 한다.
suspend()
& resume()
& stop()
위 3개의 메서드들은 쓰레드 실행 제어하는 가장 손쉬운 방법일 순 있지만
suspend()
와stop()
메서드는
" 교착 상태 "를 야기할 가능성이 높기 때문에 Java API 문서에 Deprecated 되어 있다.
( 간단한 프로그래밍의 경우엔 사용해도 무관하지만 그래도 왠만하면 사용하지 않는 것을 권장)
suspend()
: sleep()
처럼 쓰레드를 일시 정지resume()
: suspend()
에 의해 정지된 쓰레드를 다시 실행 대기 상태로 전환stop()
: 호출되는 즉시 쓰레드 종료