[Java 자바] 12. 멀티 스레드 ② 동기화, 스레드 상태와 제어
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();