Programming/Java

[Java 자바] 12. 멀티 스레드 ② 동기화, 스레드 상태와 제어

erinh 2022. 9. 12. 15:19
반응형

12-4. 동기화 메소드와 동기화 블록

12-4-1. 공유 객체를 사용할 때 주의점

- 싱글 스레드 프로그램에서는 한 개의 스래드가 객체를 전부 사용하지만,
   멀티 스레드 프로그램에서는 스레드들이 객체를 공유해서 작업해야 하는 경우 발생

- 이 경우, 다른 스레드에 의해 객체가 상태 변경이 되어 의도와 다른 결과가 산출될 수 있기 때문에 주의해야 함

12-4-2. 동기화 메소드 및 동기화 블록

- 임계 영역 (critical section): 멀티 스레드 프로그램에서 단 하나의 스레드만 실행할 수 있는 코드 영역

 => 자바에서는 이를 지정하기 위해 동기화 메소드와 동기화 블록을 제공

 => 스레드가 객체 내부의 동기화 메소드 또는 블록에 들어가면 즉시 객체에 잠금을 걸어 다른 스레드가 임계 영역의 코드를 실행하지 못 함

// 1. 동기화 메소드 만들기 (synchronized 키워드 활용)
public synchronized void method() {
  임계 영역; // 단 하나의 스레드만 실행
}

// 2. 동기화 블록 (메소드의 일부 내용만 임계 영역으로 만들 경우)
public void method() {
  // 여러 스레드가 실행 가능한 영역
  ...
  synchronized(공유객체) {			// 공유객체가 자신이면 this 삽입 가능
    임계 영역 // 단 하나의 스레드만 실행
  }
  // 여러 스레드가 실행 가능한 영역
  ...
}

12-5. 스레드 상태

- 실행 대기: start() 메소드 호출 (스케줄링이 되지 않아 실행을 기다리는 상태)

- 실행: 스레드 스케줄링으로 선택된 스레드가 CPU를 점유하고 run() 메소드를 실행 

=> 실행 대기와 실행을 번갈아가면서 스레드의 run() 메소드가 조금씩 실행됨

- 종료: 더 이상 실행할 코드가 없는 상태

- 일시정지: 스레드가 실행할 수 없는 상태

// getState() 메소드를 통해 스레드 상태에 대한 열거 상수를 리턴받을 수 있음
Thread.State state = targetThread.getState();
상태 열거 함수 설명
객체 생성 NEW 스레드 객체가 생성, 아직 start() 메소드가 호출되지 않은 상태
실행 대기 RUNNABLE 실행 상태로 언제든지 갈 수 있는 상태
일시 정지 WAITING 다른 스레드가 통지할 때까지 기다리는 상태
TIMED_WAITING 주어진 시간 동안 기다리는 상태
BLOCKED 사용하고자 하는 객체의 락이 풀릴 때까지 기다리는 상태
종료 TERMINATED 실행을 마친 상태

12-6. 스레드 상태 제어

: 미디어 플레이어에서 동영상을 보다가 일시 정지 시키거나 종료시킬 수도 있는 것처럼, 스레드의 상태를 변경하는 것

12-6-1. 주어진 시간 동안 일시 정지 (sleep())

- 실행 중인 스레드를 일정 시간 멈추게 하고 싶을 때 (밀리세컨드 단위로 입력)

try {
  Thread.sleep(1000);
} catch(InterruptedException e) {
  // 일시 정지 상태에서 주어진 시간이 되기전에 interrupt() 메소드가 호출되면 실행
}

12-6-2. 다른 스레드에게 실행 양보 (yield())

- 반복문들이 무의미한 반복을 계속할 경우, 다른 스레드에게 실행을 양보하고 자신은 실행 대기 상태로 가게 하는 것

public void run() {
  while(true) {
    if(work) {
      System.out.println("ThreadA 작업 내용")
    } else {
      Thread.yield();
    }
  }
}

12-6-3. 다른 스레드의 종료를 기다림 (join())

- 다른 스레드가 종료될 때까지 기다렸다가 실행해야 하는 경우 사용 (다른 스레드의 계산 결과값을 활용해야할 때 등)

try {
  sumThread.join(); 	// sumThread가 종료될 때까지 메인스레드를 일시 정지
} catch (InterruptedException e) {
}

12-6-4. 스레드 간 협업 (wait(), notify(), notifyAll())

- 경우에 따라 두 개의 스레드를 교대로 번갈아며 실행해야 할 경우, 공유 객체를 활용

- 공유 객체는 두 스레드가 작업할 내용을 각각 동기화 메소드로 구분해 놓음

 1) 한 스레드가 작업을 완료하면 notify() 메소드를 호출해 일시 정지 상태의 다른 스레드를 실행 대기 상태로 만듦

 2) 자신은 두 번 작업하지 않도록 wait() 메소드를 호출하여 일시 정지 상태로 만듦

     ( wait(long timeout) 이나 wait(long timeout, int nanos)를 활용하면 자동으로 시간이 지나면 실행 대기 상태가 됨)

// WorkObject.java
public class WorkObject {
  public synchronized void methodA() {
    System.out.println("ThreadA의 methodA() 작업 실행");
    notify();
    try {
      wait();
    } catch {InterruptedException e) {
    }
  }
  
  public synchronized void methodB() {
    System.out.println("ThreadB의 methodB() 작업 실행");
    notify();
    try {
      wait();
    } catch {InterruptedException e) {
    }
  }
}

// ThreadA.java
public class ThreadA extends Thread {
  private WorkObject workobject;
   
  public ThreadA(WorkObject workObject) {
    this.workObject = workObject;
  }
  
  @Override
  public void run() {
    for(int i = 0; i < 10; i++) {
      workObject.methodA();
    }
  }
}

// ThreadB.java
public class ThreadB extends Thread {
  private WorkObject workobject;
   
  public ThreadB(WorkObject workObject) {
    this.workObject = workObject;
  }
  
  @Override
  public void run() {
    for(int i = 0; i < 10; i++) {
      workObject.methodB();
    }
  }
}

// WaitNotifyExample.java
public class WaitNotifyExample {
  public static void main(String[] args) {
    WorkObject sharedObject = new WorkObject();
    
    ThreadA threadA = new ThreadA(sharedObject);
    ThreadB threadB = new ThreadB(sharedObject);
    
    threadA.start();
    threadB.start();
  }
}

12-6-5. 스레드의 안전한 종료 (stop 플래그, interrupt())

① stop 플래그를 이용하는 방법

- run() 메소드가 정상적으로 종료되도록 유도하는 것

public class XXXThread extends Thread {
  private boolean stop; // 스탑 플래그 필드
  
  public void run() {
    while(!stop) {
      스레드가 반복 실행하는 코드;
    }
    // 스레드가 사용한 자원 정리
  }
}

② interrupt() 메소드를 이용하는 방법

- 스레드가 일시 정지 상태에 있을 때 InterruptedException 예외를 발생시키는 역할

// InterruptExample.java
public class InterruptExample {
  public static void main(String[] args) {
    Thread thread = new PrintThread2();
    thread.start();
    
    try { Thread.sleep(1000);} catch (InterruptedException e) {}
    thread.interrupt();
  }
}

// PrintThread.java
public class PrintThread extends Thread {
  public void run() {
    try {
      while(true) {
        System.out.println("실행 중");
        Thread.sleep(1);
      }
    } catch(InterruptedException e) {
    }
   
    System.out.println("자원 정리");
    System.out.println("실행 종료");
  }
}

- interrupt() 호출 여부 확인하는 메소드

boolean status = Thread.interrupted();
boolean status = objThread.isInterrupted();

 

반응형