여러 thread가 실행될 때 Thread 클래스에서 제공하는 다양한 메서드를 통해 쓰레드들끼리의 실행 순서를 정하거나, 실행을 일시정지시키는 등 실행 제어가 가능합니다. 이번 포스팅은 쓰레드 실행 제어에 관련 있는 메서드에 대해서 알아보겠습니다!
Thread 실행 제어 메서드 종류
메서드 | 설명 |
static void sleep(long millis) static void sleep(long millis, int nanos) |
지정된 시간동안 쓰레드를 일시정지 시킵니다. 지정한 시간이 지나면 자동적으로 다시 실행대기상태가 됩니다. (millis: 1000분의 일초, nanos:10억분의 일초) |
void join() void join(long millis) void join(long millis, int nanos) |
지정된 시간동안 쓰레드가 실행되도록 합니다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속합니다. |
void interrupt() | sleep()나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만듭니다. 해당 쓰레드에서는 InterruptedException이 발생함으로써 일시정지상태를 벗어납니다. |
static void yield() | 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보(yield)하고 자신은 실행대기상태가 됩니다. |
sleep()
static의 sleep() 메서드이므로 Thread.sleep(시간)으로 사용해야 하고, interrupt()가 호출되거나 지정된 시간이 다 되었을 때 잠에서 깨어나 실행대기 상태가 되므로, try-catch 문으로 InterruptedException이 발생할 것을 대비해야 합니다. 즉, 아래의 코드처럼 작성합니다.
void delay(){
try{
Thread.sleep(2000); // 2초동안 sleep
}catch(InterruptedException e) {}
}
interrupt()
interrupt()는 실행 중인 쓰레드의 작업이 끝나기 전에 작업을 중단하여 취소를 요청하는 메서드입니다. 따라서, 쓰레드를 강제로 종료하지 못하고 그저 실행 중인 쓰레드를 interrupted 상태로 변경하는 역할을 합니다.
여기서 쓰레드가 interrupt된 상태인지의 여부를 확인하는 메서드가 2개가 있습니다. 아래 표로 확인해 보겠습니다.
메서드 | 의미 |
void interrupt() | 쓰레드의 interrupted 상태를 false에서 true로 변경 |
boolean isInterrupted() | 쓰레드의 interrupted 상태를 반환 |
static boolean interrupted() | 쓰레드의 interrupted 상태를 반환하고, 쓰레드의 interrupted 상태를 false로 변경 |
아래와 같이 코드를 작성하면 사용자가 숫자를 입력하기 전까지 쓰레드는 계속 sleep 상태에 있습니다. 숫자를 입력하면 while문을 빠져나가고 thread_1이 종료됩니다.
public class Main {
public static void main(String[] args) {
Runnable r = new Thread_1();
Thread thread_1 = new Thread(r);
thread_1.start();
Scanner scanner = new Scanner(System.in);
System.out.println("숫자를 입력하세요.");
scanner.nextInt();
thread_1.interrupt();
}
}
class Thread_1 implements Runnable {
@Override
public void run() {
while (!currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted!!");
currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + " ended");
}
}
여기서 주의할 점은 InterruptedException이 발생했을 때도 interrupted() 메서드처럼 쓰레드의 interrupted 상태를 false로 초기화한다는 점입니다. 따라서 catch 문에 다시 쓰레드에 interrupt()를 호출해야 쓰레드가 interrupted 상태가 되고, while문을 빠져나올 수 있습니다.
yield()
쓰레드가 자신에게 주어진 실행시간을 다음 차례의 쓰레드에게 양보하도록 하는 메서드입니다. 아래와 같이 코드를 작성하면, 자기 자신이 일시정지 상태에 머물러 있을 때 남은 실행시간을 다른 쓰레드에게 양보(yield) 함으로써 낭비하는 시간 없이 효율적으로 프로그램을 실행시킬 수 있습니다.
class Thread_1 implements Runnable {
boolean stopped = false;
@Override
public void run() {
stopped = false;
while(!stopped) {
if (!interrupted()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted!!");
currentThread().interrupt();
}
}
else{
Thread.yield();
}
}
System.out.println(Thread.currentThread().getName() + " ended");
}
}
join()
쓰레드 A와 B가 있다고 가정해 보겠습니다. 쓰레드 A가 join()을 호출하면 자신이 하던 작업을 일시정지하고 다른 쓰레드(B)가 작업을 수행하도록 합니다. 쓰레드 A는 B의 작업이 끝날때까지 기다리거나 join() 호출 시 시간을 지정하였다면 지정한 시간동안 쓰레드 B가 작업을 수행하도록 하는 메서드입니다.
sleep()과 유사하게 join() 상태에서 interrupted 되어 대기 상태에서 깨어나는 것이 가능하기 때문에 try-catch문으로 감싸서 사용합니다. sleep()과 다른 점은, sleep()은 static 메서드인 반면, join()은 인스턴스 메서드입니다. 즉, sleep()은 현재 쓰레드에 대하여 동작하지만, join()은 특정 쓰레드에 대하여 동작하도록 할 수 있습니다.
아래 코드를 통해 확인하겠습니다.
public class Main {
public static void main(String[] args) {
Runnable r = new Thread_0();
Thread thread_1 = new Thread(r);
thread_1.start();
try {
thread_1.join(); // main 대기상태, thread_0은 작업 수행
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " interrupted");
}
System.out.println("Main thread ended");
}
}
class Thread_0 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " started.");
Runnable r2 = new Thread_1();
Thread thread_2 = new Thread(r2);
thread_2.start();
try {
thread_2.join(); // thread_0 대시 상태, thread_2 작업 수행
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " interrupted");
}
for (int i = 0; i < 3; ++i) System.out.println(Thread.currentThread().getName() + " is running");
System.out.println(Thread.currentThread().getName() + " ended.");
}
}
class Thread_1 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " started.");
for (int i = 0; i < 3; ++i) System.out.println(Thread.currentThread().getName() + " is running");
System.out.println(Thread.currentThread().getName() + " ended.");
}
}
[결과]
Thread-0 started.
Thread-1 started.
Thread-1 is running
Thread-1 is running
Thread-1 is running
Thread-1 ended.
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 ended.
Main thread ended
[참고자료]
남궁 성, [Java의 정석 3rd Edition], 도우출판, 2016
'Java' 카테고리의 다른 글
synchronized를 이용한 쓰레드의 동기화 (2) | 2023.10.29 |
---|---|
제네릭(Generic) 타입의 형변환 (0) | 2023.10.29 |
제네릭(Generic)이란? (0) | 2023.10.25 |
Thread 구현과 실행 (Runnable vs Thread) (0) | 2023.10.25 |
자바의 애너테이션(Annotation) (2) | 2023.10.22 |