사용자는 미디어 플레이어에서 동영상을 보다가 일시 정지시킬 수도 있고, 종료시킬 수도 있다. 일시 정지는 조금 후 다시 동영상을 보겠다는 의미이므로 미디어 플레이어는 동영상 스레드를 일시 정지 상태로 만들어야 한다. 그리고 종료는 더 이상 동영상을 보지 않겠다는 의미이므로 미디어 플레이어는 스레드를 종료 상태로 만들어야 한다. 이와 같이 실행 중인 스레드의 상태를 변경하는 것을 스레드 상태 제어라고 한다. 멀티 스레드 프로그램을 만들기 위해서는 정교한 스레드 상태 제어가 필요한데, 상태 제어가 잘못되면 프로그램은 불안정해져서 먹통이 되거나 다운된다. 멀티 스레드 프로그래밍이 어렵다고 하는 이유가 바로 여기에 있다. 스레드는 잘 사용하면 약이 되지만, 잘못 사용하면 치명적인 프로그램 버그가 되기 때문에 스레드를 정확하게 제어하는 방법을 잘 알고 있어야 한다. 스레드 제어를 제대로 하기 위해서는 스레드의 상태 변화를 가져오는 메소드들을 파악하고 있어야 한다. 다음 그림은 상태 변화를 가져오는 메소드의 종류를 보여준다.
위 그림에서 취소선을 가진 메소드는 스레드의 안전성을 해친다고 하여 더 이상 사용하지 않도록 권장된 Deprecated 메소드들이다.
메소드 | 설명 |
interrupt() | 일시 정지 상태의 스레드에서 InterruptedException 예외를 발생시켜, 예외 처리 코드(catch)에서 실행 대기 상태로 가거나 종료 상태로 갈 수 있도록 한다. |
notify() norifyAll() |
동기화 블록 내에서 wait() 메소드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다. |
resume() | suspend() 메소드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다. - Deprecated(대신 notify(), notifyAll() 사용) |
sleep(long millis) sleep(long millis, int nanos) |
주어진 시간 동안 스레드를 일시 정지 상태로 만든다. 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다. |
join() join(long millis) join(long millis, int nanos) |
join() 메소드를 호출한 스레드는 일시 정지 상태가 된다. 실행 대기 상태로 가려면, join() 메소드를 멤버로 가지는 스레드가 종료되거나, 매개값으로 주어진 시간이 지나야 한다. |
wait() wait(long millis) wait(long millism, int nanos) |
동기화(Synchronized) 블록 내에서 스레드를 일시 정지 상태로 만든다. 매개값으로 주어진 시간이 지나면 자동적으로 실행 상태가 된다. 시간이 주어지지 않으면 notify(), notifyAll() 메소드에 의해 실행 대기 상태로 갈 수 있다. |
suspend() | 스레드를 일시 정지 상태로 만든다. resume() 메소드를 호출하면 다시 실행 대기 상태가 된다. - Deprecated(대신 wait() 사용) |
yield() | 실행중에 우선순위가 동일한 다른 스레드에게 실행을 양보하고 실행 대기 상태가 된다. |
stop() | 스레드를 즉시 종료시킨다. - Depecated |
위 표에서 wait(), notify(), notifyAll() 은 Object 클래스의 메소드이고, 그 이외의 메소드는 모두 Thread 클래스의 메소드들이다. wait(), notify(), notifyAll() 메소드의 사용 방법은 스레드의 동기화에서 자세히 설명하기로 하고, 이번 절에서는 Thread 클래스의 메소드들만 살펴볼 것이다.
주어진 시간동안 일시정지(sleep())
실행 중인 스레드를 일정 시간 멈추게 하고 싶다면 Thread 클래스의 정적 메소드인 sleep()을 사용하면 된다. 다음과 같이 Thread.sleep() 메소드를 호출한 스레드는 주어진 시간동안 일시 정지 상태가 되고, 다시 실행 대기 상태로 돌아간다.
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
// interrupt() 메소드가 호출되면 실행
}
매개값에는 얼마 동안 일시 정지 상태로 있을 것인지, 밀리세컨드(1/1000) 단위로 시간을 주면 된다. 위와 같이 1000이라는 값을 주면 스레드는 1초가 경과할 동안 일시 정지 상태로 있게 된다. 일시 정지 상태에서 주어진 시간이 되기 전에 interrupt() 메소드가 호출되면 InterruptException이 발생하기 때문에 예외 처리가 필요하다. 다음 예제는 3초 주기로 비프(beep)음을 10번 발생 시킨다.
// SleepExample.java -- 3초 주기로 10번 비프음 발생
import java.awt.Toolkit;
public class SleepExample {
public static void main(String[] args) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
for(int i=0; i<10; i++) {
toolkit.beep();
try {
Thread.sleep(3000);
} catch(InterruptedException e) {}
}
}
}
8~11 라인은 메인 스레드를 3초 동안 일시 정지 상태로 보내고, 3초가 지나면 다시 실행 준비 상태로 돌아오도록 했다. 만약 비프 소리가 나지 않으면, 사운드 카드가 제대로 설치되었는지, 스피커가 켜져 있는지 확인해보길 바란다.
다른 스레드에게 실행 양보(yield())
스레드가 처리하는 작업은 반복적인 실행을 위해 for문이나 while문을 포함하는 경우가 많다. 가끔은 이 반복문들이 무의미한 반복을 하는 경우가 있다. 다음 코드를 보면서 이해해보자.
public void run() {
while(true) {
if(work) {
System.out.println("ThreadA 작업 내용");
}
}
}
스레드가 시작되어 run() 메소드를 실행하면 while(true) { } 블록을 무한 반복 실행한다. 만약 work의 값이 false라면 그리고 work의 값이 false에서 true로 변경되는 시점이 불명확하다면, while문은 어떠한 실행문도 실행하지 않고 무의미한 반복을 한다. 이것보다는 다른 스레드에게 실행을 양보하고 자신은 실행 대기 상태로 가는 것이 전체 프로그램 성능에 도움이 된다. 이런 기능을 위해서 스레드는 yield() 메소드를 제공하고 있다. yield() 메소드를 호출한 스레드는 실행 대기 상태로 돌아가고 동일한 우선순위 또는 높은 우선순위를 갖는 다른 스레드가 실행 기회를 가질 수 있도록 해준다.
다음 코드는 의미 없는 반복을 줄이기 위해 yield() 메소드를 호출해서 다른 스레드에게 실행 기회를 주도록 수정한 것이다.
public void run() {
while(true) {
if(work) {
System.out.println("ThreadA 작업 내용");
} else {
Thread.yield();
}
}
}
다음 예제에서는 처음 실행 후 3초 동안은 ThreadA와 ThreadB가 번갈아가며 실행된다. 3초 뒤에 메인 스레드가 ThreadA의 work 필드를 false로 변경함으로써 ThreadA는 yield() 메소드를 호출한다. 따라서 이후 3초 동안에는 ThreadB가 더 많은 실행 기회를 얻게 된다. 메인 스레드는 3초 뒤에 다시 ThreadA의 work 필드를 true로 변경해서 ThreadA와 ThreadB가 번갈아가며 실행하도록 한다. 마지막으로 메인 스레드는 3초 뒤에 ThreadA와 ThreadB의 stop 필드를 true로 변경해서 두 스레드가 반복 작업을 중지하고 종료하도록 한다.
// YieldExample.java -- 스레드 실행 양보 예제
public class YieldExample {
public static void main(String[] args) {
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
threadA.start();
threadB.start();
try { Thread.sleep(3000); }
catch(InterruptedException e) {}
threadA.work = false;
try { Thread.sleep(3000); }
catch(InterruptedException e) {}
threadA.work = true;
try { Thread.sleep(3000); }
catch(InterruptedException e) {}
threadA.stop = true;
threadB.stop = true;
}
}
// ThreadA.java -- 스레드 실행 양보 예제
public class ThreadA extends Thread {
public boolean stop = false; // 종료 플래그
public boolean work = true; // 작업 진행 여부 플래그
public void run() {
while(!stop) {
if(work) {
System.out.println("ThreadA 작업 내용");
} else {
Thread.yield();
}
}
System.out.println("ThreadA 종료");
}
}
// ThreadB.java -- 스레드 실행 양보 예제
public class ThreadB extends Thread {
public boolean stop = false; // 종료 플래그
public boolean work = true; // 작업 진행 여부 플래그
public void run() {
while(!stop) {
if(work) {
System.out.println("ThreadB 작업 내용");
} else {
Thread.yield();
}
}
System.out.println("ThreadB 종료");
}
}
'Java 길찾기 > 이것이 자바다' 카테고리의 다른 글
[Java] 스레드 상태 제어 - stop 플래그, interrupt() (0) | 2022.03.22 |
---|---|
[Java] 스레드 상태 제어 - join(), wait(), notify(), notifyAll() (0) | 2022.03.21 |
[Java] 스레드 상태 (0) | 2022.03.17 |
[Java] 동기화 메소드와 동기화 블록 (0) | 2022.03.16 |
[Java] 스레드 우선순위 (0) | 2022.03.15 |