본문으로 건너뛰기
Advertisement

쓰레드 동기화와 제어 (Synchronization)

멀티 쓰레드 환경에서는 여러 쓰레드가 프로세스의 같은 자원(메모리 등)을 공유하기 때문에 예상치 못한 문제가 발생할 수 있습니다. 이를 해결하기 위해 하나의 쓰레드가 특정 작업을 마칠 때까지 다른 쓰레드가 접근하지 못하도록 막는 것을 동기화(Synchronization) 라고 합니다.

1. synchronized 키워드

메서드 전체나 특정 영역(블록)에 임계 영역(Critical Section)을 설정하여, 한 번에 하나의 쓰레드만 이 영역을 실행하도록 보장할 수 있습니다.

메서드에 synchronized 적용

public synchronized void withdraw(int money) {
if(balance >= money) {
try { Thread.sleep(1000); } catch(Exception e) {}
balance -= money;
}
}

특정한 블록에 synchronized 적용

public void withdraw(int money) {
synchronized(this) { // 이 객체의 lock을 얻은 쓰레드만 블럭 안으로 진입
if(balance >= money) {
try { Thread.sleep(1000); } catch(Exception e) {}
balance -= money;
}
}
}

위와 같이 동기화를 사용하면 경쟁 상태(Race Condition)를 방지하여 데이터의 무결성을 지킬 수 있습니다.

2. wait()와 notify()

동기화를 통해 한 가지 문제를 해결했지만, 어떤 쓰레드가 Lock을 독점하여 오랫동안 쥐고 있게 되면 다른 쓰레드들은 작업이 진행되지 못하는 현상(기아 현상, Starvation)이 발생할 수 있습니다.

이 문제를 해결하기 위해 쓰레드를 제어하는 메서드가 wait()notify() 입니다.

  • wait(): 실행 중이던 쓰레드를 대기 상태로 만들고 객체의 락을 반납합니다.
  • notify(): 대기 중인 쓰레드 중 하나를 깨워 실행 가능한 상태로 만듭니다.
class Table {
// 음식 리스트가 꽉 차면 요리사는 요리를 멈추고 기다려야 함
public synchronized void add(String dish) {
if(dishes.size() >= MAX_FOOD) {
try {
wait(); // COOK 쓰레드는 대기
} catch(InterruptedException e) {}
}
dishes.add(dish);
notify(); // 음식을 추가했으니 CUSTOMER 쓰레드를 깨움
}
}

3. 쓰레드의 생명주기 및 제어 메서드

쓰레드는 상태를 가지며, 다음과 같은 필수 메서드들을 사용해 실행을 제어할 수 있습니다.

  • sleep(long millis): 지정된 시간 동안 현재 쓰레드를 일시정지 상태로 만듭니다.
  • join(): 다른 쓰레드의 작업이 끝날 때까지 기다립니다.
  • interrupt(): sleep, wait, join 등으로 일시정지 상태인 쓰레드를 깨워서 실행 대기 상태로 만듭니다.
  • yield(): 자신에게 할당된 실행 시간을 다음 쓰레드에게 양보합니다.
// join()을 사용해 두 쓰레드의 작업 시간을 측정하는 예제
Thread t1 = new Thread(new Worker());
Thread t2 = new Thread(new Worker());

t1.start();
t2.start();

long startTime = System.currentTimeMillis();

try {
t1.join(); // main 쓰레드는 t1 작업 완료를 기다림
t2.join(); // main 쓰레드는 t2 작업 완료를 기다림
} catch (InterruptedException e) {}

System.out.print("소요시간: " + (System.currentTimeMillis() - startTime));

이러한 동기화와 흐름 제어 도구들을 정확하게 이해하는 것이 자바 멀티 쓰레드 프로그래밍의 핵심입니다.

Advertisement