[Java] 자바 스레드 상태 제어 메서드 종류 및 사용법
Intro
스레드를 생성하고 실행시키기 위해서는 start() 메서드를 호출해야 한다.
엄밀히 말하면 start() 메서드는 스레드를 실행시키는 것이 아닌 실행 대기 상태로 만들어주는 메서드이다.
따라서, start() 메서드가 호출되면 해당 스레드는 대기 상태가 되고, 운영체제가 스레드를 실행시킨다. 이는 스레드에는 상태가 존재한다는 것을 알 수 있으며, 이러한 스레드의 상태를 바꿀 수 있는 메서드가 존재한다.
스레드의 상태 제어 메서드
스레드 상태를 제어하기 위한 여러 가지 메서드가 존재한다.
스레드를 실행 대기 상태로 만드는 start(), notify(), interrupt(), yield() 메서드,
실행 중인 스레드를 잠시 멈추는 sleep(), wait(), join() 메서드가 있다.
sleep(long milliSecond)
sleep() 메서드는 Thread의 클래스 메서드로 인자에 입력한 수의 milliSecond 동안 스레드를 멈춘다.
// 1000 milliSecond 동안 스레드 일시 정지
Thread.sleep(1000);
sleep() 메서드를 실행하면 실행한 스레드의 상태는 일시 정지 상태로 전환되며, 인자로 전달한 시간이 경과하거나, interrupt() 메서드를 호출한 경우에 다시 실행 대기 상태로 복귀한다.
interrupt() 메서드를 통해 실행 대기 상태로 전환하려면 try - catch문을 사용하여 예외를 처리해야 한다.
try {
Thread.sleep(1000);
} catch (Exception e) {
...
}
스레드를 얼마나 멈출 것인지 설정할 수 있으나, 약간의 오차는 발생할 수 있다.
interrupt()
interrupt() 메서드는 일시 중지 상태인 스레드를 실행 대기 상태로 복귀시킨다. sleep(), wait(), join() 메서드에 의해 일시 정지된 스레드들은 각 해당 메서드에서 정지한다.
따라서 정지 중인 스레드가 아닌 다른 스레드에서 ‘정지스레드.interrupt()’를 호출하여 정지 상태를 실행 대기 상태로 전환할 수 있다.
public class InterruptEx {
public static void main(String[] args) {
Thread thread = new Thread() {
public void run() {
try {
while (true) Thread.sleep(10000);
} catch (Exception e) {}
System.out.println("정지 상태 해제");
}
};
// 생성된 상태
System.out.println(thread.getState());
// 스레드 실행
thread.start();
// 실행 상태
System.out.println(thread.getState());
// 정지 상태
while (true) {
if (thread.getState() == Thread.State.TIMED_WAITING) {
System.out.println(thread.getState());
break;
}
}
// 인터럽트 메서드 실행
thread.interrupt();
// 실행 상태
while (true) {
if (thread.getState() == Thread.State.RUNNABLE) {
System.out.println(thread.getState());
break;
}
}
// 정지 상태
while (true) {
if (thread.getState() == Thread.State.TERMINATED) {
System.out.println(thread.getState());
break;
}
}
}
}
// 출력
NEW
RUNNABLE
TIMED_WAITING
RUNNABLE
정지 상태 해제
TERMINATED
처음 생성된 상태에서 실행을 하면 RUNNABLE(실행) 상태가 된다.
이후 sleep() 메서드로 인해 TIMED_WAITING(정지) 상태로 전환되고,
interrupt() 메서드로 인해 실행 강제 상태로 전환된다.
마지막으로 “정지 상태 해제” 메시지를 출력한 후 종료되어 TERMINATE(소멸) 상태가 된다.
yield()
yield() 메서드는 다른 스레드에게 실행을 양보한다.
예를 들어, 운영 체제의 스케줄러에 의해 10초를 할당받은 스레드가 있다면,
3초 동안 작업을 수행하다가 yield() 메서드를 호출하면, 남은 7초는 다음 스레드에게 양보하는 것이다.
따라서 실행 중인 상태에서 yield() 호출되면 실행 대기 상태로 전환된다.
join() / join(long milliSecond)
join() 메서드는 특정 스레드가 작업하는 동안에 자신을 일시 정지 상태로 전환하는 메서드이다. 즉, 다른 스레드의 작업이 끝날 때까지 기다린다.
join() 메서드는 인자로 밀리초 단위로 전달할 수 있으며, 전달한 인자만큼의 시간이 경과하거나 interrupt() 메서드가 호출되거나, join() 호출 시 지정했던 다른 스레드가 모든 작업을 마치면 다시 실행 대기 상태로 복귀한다.
sleep() 메서드와 유사한 점이 존재하지만, join() 메서드는 특정 스레드에 대해 동작하는 인스턴스 메서드이다.
// sleep()
Thread.sleep(1000);
// join()
instanceThread.join();
public class JoinEx {
public static void main(String[] args) {
SumThread sumThread = new SumThread();
sumThread.setEnd(100);
sumThread.start();
// 메인 스레드는 sumThread가 끝날 때 까지 대기
try {
sumThread.join();
} catch (Exception e) {}
System.out.printf("1부터 %d까지 합 : %d", sumThread.getEnd(), sumThread.getSum());
}
}
class SumThread extends Thread {
private long sum;
private int end;
public long getSum() {
return sum;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
public void run() {
for (int i = 0; i <= end; i++) {
sum += i;
}
}
}
wait() / notify()
wait()와 notify() 메서드는 두 스레드가 교대로 작업을 처리해야 할 때 사용하는 메서드이다.
예를 들어, 두 스레드 A와 B가 하나의 공유 객체를 두고 협업을 하는 상황이라면,
A가 먼저 작업을 한 뒤 완료하면, B와 교대하기 위해 notify() 메서드를 호출한다.
notify() 메서드가 호출되면 B는 실행 대기 상태로 전환되며, A는 wait() 메서드를 호출하여 자기 자신을 정지 상태로 만든다.
이러한 과정을 반복하여 두 스레드가 공유 객체에 대해 배타적으로 접근하여 실행할 때 사용한다.
public class WaitAndNotifyEx {
public static void main(String[] args) {
ShareObject object = new ShareObject();
ThreadA threadA = new ThreadA(object);
ThreadB threadB = new ThreadB(object);
threadA.start();
threadB.start();
}
}
// 공유 객체를 생성할 클래스
class ShareObject {
public synchronized void actionA() {
System.out.println("threadA의 actionA 동작");
notify();
try { wait(); } catch(Exception e) {}
}
public synchronized void actionB() {
System.out.println("threadB의 actionB 동작");
notify();
try { wait(); } catch(Exception e) {}
}
}
// ThreadA
class ThreadA extends Thread {
private ShareObject object;
public ThreadA(ShareObject object) {
this.object = object;
}
public void run() {
for(int i = 0; i < 5; i++) {
object.actionA();
}
}
}
// ThreadB
class ThreadB extends Thread {
private ShareObject object;
public ThreadB(ShareObject object) {
this.object = object;
}
public void run() {
for(int i = 0; i < 5; i++) {
object.actionB();
}
}
}
// 출력
threadA의 actionA 동작
threadB의 actionB 동작
threadA의 actionA 동작
threadB의 actionB 동작
threadA의 actionA 동작
threadB의 actionB 동작
threadA의 actionA 동작
threadB의 actionB 동작
threadA의 actionA 동작
threadB의 actionB 동작
다음과 같이 번갈아 가면서 실행하게 된다.