[JAVA] 쓰레드 ( Thread ) ④

DongGyu Jung·2022년 3월 28일
0

자바(JAVA)

목록 보기
47/60
post-thumbnail

🏃‍♂️ 들어가기 앞서..

본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 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가지다.

  1. time out : 지정 시간 경과
  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() : 호출되는 즉시 쓰레드 종료

0개의 댓글