Thread (2)

Drumj·2023년 2월 21일
0

오늘의 학습

이전글과 이어서 자바의 정석 챕터13 Thread를 공부하며...


데몬 쓰레드(Daemon Thread)

다른 일반 쓰레드(데몬 쓰레드가 아닌 쓰레드)의 작업을 돕는 보조적인 역할을 수행하는 쓰레드이다.

일반 쓰레드가 모두 종료되면 데몬 쓰레드는 강제적으로 종료된다.
이는 데몬이 일반의 보조역할을 수행하기 때문에 일반 쓰레드가 모두 종료되면 데몬쓰레드의 존재 의미가 없기 때문이라고 한다.

이 점을 제외한다면 일반쓰레드와 다른점은 없다.

일반 쓰레드와 작성법과 실행법이 같으며 쓰레드를 생성한 다음 실행하기 전에setDaemon(true)를 호출 하기만 하면 된다.
또한 데몬 쓰레드가 생성한 쓰레드는 자동으로 데몬 쓰레드가 된다.

boolean isDaemon() //쓰레드가 데몬인지 확인. 맞다면 true를 반환
void setDaemon(boolean on) //쓰레드를 데몬 쓰레드 or 사용자 쓰레드로 변경.
//매개변수 on의 값을 true로 지정하면 데몬 쓰레드가 된다.

쓰레드의 실행제어

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

쓰레드의 스케줄링과 관련된 메서드를 알아보자


sleep

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

지정된 시간동안 쓰레드를 멈추게 한다.
sleep()에 의해 일시정지 상태가 된 쓰레드는 지정된 시간이 지나거나 interrupt()가 호출되면,
InterruptedException이 발생되어 잠에서 깨어나 실행대기 상태가 된다.
그래서 항상 try-catch 문으로 예외를 처리해줘야 한다.

sleep()은 항상 현재 실행 중인 쓰레드에 대해 작동한다.


interrupt()

진행 중인 쓰레드의 작업이 완료되기 전에 취소시켜야 할 때 사용.
interrupt()는 작업을 멈추라고 요청하지 강제로 종료시키는 것은 아니다.

void interrupt() // thread의 interrupted 상태를 false 에서 true로 변경
boolean isInterrupted() // thread의 interrupted 상태를 반환
static boolean interrupted() // 현재 thread의 interrupted 상태를 반환 후, false로 변경

한 쓰레드가 sleep(),wait(), join() 에 의해 일시정지 상태(WAITING)에 있을 때,
이 쓰레드에 대해 interrupt()를 호출하면 sleep(),wait(), join() 에서
Interrupted Exception이 발생하고 실행대기 상태(RUNNABLE)로 바뀐다.


suspend(), resume(), stop()

suspend() : sleep()처럼 쓰레드를 멈추게 한다.
resume() : suspend()에 의해 정지된 쓰레드를 다시 실행대기 상태로 만든다.
stop() : 즉시 쓰레드가 종료된다.

위 세가지는 쓰레드의 실행을 제어하는 가장 손쉬운 방법이지만 suspend(), stop()이 교착상태(DeadLock)을 일으키기 쉽게 작성되어 있으므로 권장하지 않는다.


join()

join() : 다른 쓰레드의 작업을 기다린다.

void join()
void join(long millis)
void join(long millis, int nanos)

시간을 지정하지 않으면 해당 쓰레드가 종료될 때까지 기다린다.
작업 중에 다른 쓰레드의 작업이 먼저 수행되어야 할 필요가 있을때 join()을 사용한다.

//생략
try {
	th1.join(); //현재 실행중인 쓰레드가 th1의 작업이 끝날때까지 기다린다.
} catch (InterruptedException e) {}

여기서 중요한 점은 th1의 작업이 끝날때까지 기다리는 것이다.

join 은 sleep과 유사한 점이 많은데

현재 쓰레드가 아닌 특정 쓰레드에 대해 동작하므로 static 메서드가 아니라는 것이 다른 점이다.


yield()

yield() : 다른 쓰레드에 양보한다.

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

만약 1초동안 실행시간을 할당받은 상태에서 0.5초 동안 작업 후 yield()가 호출되면
나머지 0.5초는 포기하고 다시 실행대기 상태가 된다.


쓰레드의 동기화 (Synchronization)

멀티쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에
서로의 작업에 영향을 주게 된다.

예를 들어 Thread A가 작업하던 도중 Thread B로 제어권이 넘어갔을 때, Thread A가 작업하던 공유데이터를 Thread B가 임의로 변경하였다면, 다시 Thread A가 제어권을 받아서 나머지 작업을 마쳤을 때 원래 의도했던 것과 다른 결과를 얻을 수 있다는 것이다.

이를 방지하기 위해 한 쓰레드가 특정 작업을 끝마치기 전까지 다른 쓰레드에 의해 방해받지 않도록 하는 것이 필요하다. 그래서 도입된 개념이 임계 영역(Criticla Section)잠금(Lock) 이다.

한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 막는 것을 쓰레드의 동기화라고 한다.


synchronization 을 이용한 동기화

가장 간단한 동기화 방법으로 임계 영역을 설정하는데 사용된다.

//1. 메서드 전체를 임계 영역으로 지정
public synchronized void calcSum(){
	//임계 영역
}

//2. 특정 영역을 임계 영역으로 지정
synchronized(객체의 참조변수) {
	//임계 영역
}

첫번째 방법은 메서드 앞에 synchronized를 붙이는 방법.
메서드 전체가 임계 영역으로 설정되고 쓰레드는 메서드가 호출된 시점부터 해당 메서드가 포함된 객체의 lock을 얻어 작업을 수행하다가 메서드가 종료되면 lock을 반환한다.

두번째 방법은 메서드 내의 코드 일부를 블럭{}으로 감싸고 블럭앞에 synchronized(참조변수)를 붙이는 것. 이때 락(lock)을 걸고자하는 객체를 참조해야한다.

두 방법 모두 lock의 획득과 반납이 자동으로 이루어지므로 우린 임계 영역만 잘 설정해주면 된다.
메서드 전체를 설정하는 것 보다 블럭을 활용해서 임예 영역을 최소화 하는게 더 좋다고 한다!

synchronized를 이용한 동기화는 지정된 영역의 코드를 한 번에 하나의 쓰레드가 수행하는 것을 보장하는 것.


wait() 와 notify()

synchronized로 동기화해서 공유 데이터를 보호 했다면 이제는 특정 쓰레드가 객체의 락을 가진 상태로 오랜 시간을 보내지 않도록 하는 것도 알아보자.

wait(): 임계 영역의 코드를 수행하다가 작업을 더 이상 진행할 상황이 아니라면 쓰레드가 락을 반납하고 기다리게 한다.

notify(): 나중에 작업을 수행할 수 있는 상황이 되면 작업을 중단했던 쓰레드가 다시 락을 얻어 작업을 진행 할 수 있게 한다.

책의 예는 이렇다.
나는 소금빵을 좋아한다.
빵을 사러 갔는데 10분 뒤에 소금빵이 나온다고 한다.(하필 내가 결제할 차례에..)
그렇다면 나는 wait() 하고 다른 사람들이 먼저 결제할 수 있게 한다.
그리고 10분 뒤 소금빵이 나오면 notify()로 내가 빵을 사는 것이다.

차이가 있다면 오래 기다린 쓰레드가 락을 얻는다는 보장이 없다는 것이라 한다.

wait가 호출되면 실행 중이던 쓰레드는 대기실(waiting pool)에서 통지를 기다린다.
notify()가 호출되면 대기실에 있던 모든 쓰레드 중에서 임의의 쓰레드만 통지를 받는다.
notifyAll()은 기다리고 있는 모든 쓰레드에게 통보를 하지만 그래도 lock을 얻는 것은 하나의 쓰레드일 뿐.... 나머지는 통보를 받았으나 lock을 획득하지 못해 다시 lock을 기다리는 신세가 된다.. ㅠㅠ

wait(),notify(),notifyAll()

  • Object에 정의되어 있다.
  • 동기화 블록(synchronized 블록)내에서만 사용할 수 있다.
  • 보다 효율적인 동기화를 가능하게 한다.

마무리

이상으로 책을 공부하면서 간단하게 정리하면서 다시 복습해봤다.

더 자세한 내용은 책을 참고하는게 좋을 것 같다.
예제 코드를 보면서 실제로 어떻게 흘러가는지 보는게 아주 많은 도움이 된 듯 하다.

쓰레드에 대한 질문을 면접에서 많이 받았었는데 그냥 막연하게 일하는 노동자(?)라고만 생각했었는데....
음,,,,,, 일단 뭐 대강 흐름은 파악한 것 같다. (실제로 써먹으면서 공부하는게 더 도움이 되겠지만...)

0개의 댓글