Java/Concurrency

Java > Concurrency > 4. Thread methods

Krevis 2024. 8. 30. 10:06

쓰레드 프로그래밍이 어려운 이유는 동기화와 스케줄링 때문이다

 

효율적인 멀티 쓰레드 프로그램을 만들기 위해서는 보다 정교한 스케줄링을 통해 프로세스에게 주어진 자원과 시간을 여러 쓰레드가 낭비 없이 잘 사용하도록 프로그래밍 해야 한다

 

스케줄링을 위해 쓰레드의 상태와 상태 변경 메서드를 학습할 필요가 있다. 쓰레드의 상태는 아래 글에 설명해두었다

2024.09.05 - [Java/Concurrency] - Java > Concurrency > 3. Thread info

 

 

Thread 클래스의 메서드

void stop(), void suspend(), void resume()

교착상태를 일으키기 쉽게 작성되어 있어, 사용자제(@Deprecated) 메서드로 정해졌다

 

해당 API를 사용하지 말고 아래와 같이 유사 기능을 만들어 사용하자

https://github.com/venzersiz/learn-java8/blob/master/src/test/java/concurrency/basic/ThreadDeprecatedMethodsTest.java

static native void sleep(long millis), static void sleep(long millis, int nanos)

millis: 1000분의 1초

nanos: 10억분의 일초

 

쓰레드를 재운다

 

해당 쓰레드는 일시정지 상태가 되며 지정된 시간이 다 되거나 interrupt() 메서드가 호출되면, InterruptedException이 발생되어 잠에서 깨어나 RUNNABLE 상태가 된다

void join(), synchronized void join(long millis)

기다리게 만들 쓰레드가 다른 쓰레드 인스턴스의 join 메서드를 호출하면 된다

 

main 쓰레드에서 시간이 걸리는 다른 쓰레드를 실행하면 main 쓰레드의 작업이 먼저 끝나게 된다. 만약 main 쓰레드가 다른 쓰레드의 작업이 끝나기를 기다려야 한다면, 예를 들어 처리 결과를 받아서 처리해야한다면 어떻게 해야할까? main 쓰레드가 다른 쓰레드의 작업이 끝나길 기다려야 할 것이다. 이런 경우 사용하면 좋다

 

위와 같은 상황에서 main 쓰레드가 someThread.join() 메서드를 호출하게 되면 main 쓰레드는 someThread의 작업이 끝날 때까지 대기한다. 이때 main 쓰레드의 상태는 WAITING이 된다

 

join() 메서드 호출은 내부적으로 join(0) 메서드를 호출한다. 결국 해당 쓰레드의 작업이 종료할 때까지 무기한 대기하는 것이다

 

메서드가 호출된 쓰레드의 상태가 TERMINATED가 될 때까지 대기

 

파라미터가 있는 메서드는 대기 시간을 정해줄 때 사용한다. 이걸 사용하면 호출하는 쓰레드는 TIMED_WAITING 상태가 된다

void interrupt()

진행 중인 쓰레드의 작업이 끝나기 전에 취소시켜야할 때 사용

예를 들어 큰 파일을 다운로드할 때 시간이 너무 오래 걸리면 중간에 포기하고 취소할 수 있어야 한다

 

WAITING, TIMED_WAITING 상태의 쓰레드는 InterruptedException을 발생시켜 대기를 중단하고 RUNNABLE 상태로 만든다. 그리고 catch 절의 코드가 실행된다

 

내부적으로 interrupted 인스턴스 변수값을 false에서 true로 바꾼다

 

이 메서드 호출만으로 쓰레드가 중지되는 게 아니라 sleep 메서드 등을 사용했을 때 예외가 던저지는 것

 

https://github.com/venzersiz/learn-java8/blob/master/src/test/java/concurrency/basic/ThreadInterruptTest.java

boolean isInterrupted()

대상 쓰레드의 interrupted 인스턴스 변수값을 반환한다

 

https://github.com/venzersiz/learn-java8/blob/master/src/test/java/concurrency/basic/ThreadInterruptTest.java

static boolean interrupted()

코드가 실행되고 있는 현재 쓰레드의 interrupted 인스턴스 변수값을 반환하고 false로 변경한다

 

Thread.interrupted() 형태로 호출

 

https://github.com/venzersiz/learn-java8/blob/master/src/test/java/concurrency/basic/ThreadInterruptTest.java

static void yield()

쓰레드 자신에게 주어진 CPU의 실행시간을 다음 차례의 쓰레드에게 양보한다

 

 

Object 클래스의 메서드

final void wait(), final native void wait(long timeout), final void wait(long timeout, int nanos)

현재 쓰레드가 가진 락을 반납하고 WAITING 상태로 전환

현재 쓰레드가 synchronized 영역 내에서 락을 가지고 있을 때만 호출 가능

호출한 쓰레드는 락을 반납한다

이렇게 대기 상태로 전환된 쓰레드는 다른 쓰레드가 notify/notifyAll() 메서드를 호출할 때까지 대기한다

 

대기상태에 들어간 쓰레드를 관리하는 것을 쓰레드 대기 집합(Wait set)이라고 한다. 모든 객체 인스턴스는 각자의 모니터 락과 쓰레드 대기 집합을 가지고 있다

final native void notify()

대기 중인 쓰레드 하나를 깨운다

이 메서드는 synchronized 영역 내에서 호출되어야 한다

 

쓰레드 대기 집합에 있는 쓰레드를 하나 깨운다

깨어난 쓰레드는 여전히 임계 영역 안에 있다. 임계 영역에 있는 코드를 실행하려면 락이 필요하다. 따라서 BLOCKED 상태로 대기한다

락을 얻으면 wait() 코드 이후의 코드를 실행한다

final native void notifyAll()

대기 중인 모든 쓰레드를 깨운다

이 메서드는 synchronized 영역 내에서 호출되어야 한다

모든 대기 중인 쓰레드가 락을 얻을 수 있는 기회를 가지게 된다

 

 

쓰레드 기아(Starvation)

notify() 메서드의 문제점으로 어떤 쓰레드가 깨어날지 알 수 없기 때문에 발생

대기 상태의 쓰레드가 계속해서 실행되지 못하는 상황을 말한다

이 문제를 해결하기 위해 notifyAll() 메서드를 사용하는 방법이 있다