Java ‐ 쓰레드 제어와 생명주기 - dnwls16071/Backend_Study_TIL GitHub Wiki
📚 쓰레드 기본 정보
- Thread 클래스는 쓰레드를 생성하고 관리하는 기능을 제공한다.
public class ThreadInfoMain {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
log("thread = " + mainThread);
log("thread.threadId = " + mainThread.getId());
log("thread.getName = " + mainThread.getName());
log("thread.getPriority = " + mainThread.getPriority());
log("thread.getThreadGroup = " + mainThread.getThreadGroup());
log("thread.getState = " + mainThread.getState());
Thread thread = new Thread(new HelloRunnable(), "thread");
log("thread = " + thread);
log("thread.threadId = " + thread.getId());
log("thread.getName = " + thread.getName());
log("thread.getPriority = " + thread.getPriority());
log("thread.getThreadGroup = " + thread.getThreadGroup());
log("thread.getState = " + thread.getState());
}
}
- 쓰레드가 제공하는 정보
- 쓰레드 이름
- 쓰레드 객체 정보
- 쓰레드 ID
- 쓰레드 우선순위
- 쓰레드 그룹
- 쓰레드 상태
📚 쓰레드 생명 주기
- 쓰레드의 상태
- NEW(새로운 상태) : 쓰레드가 생성은 되었으나 아직 실행되지 않은 상태
- Runnable(실행 가능 상태) : 쓰레드가 실행 중이거나 실행될 준비가 된 상태
- 일시 중지 상태들
- Blocked(차단 상태) : 쓰레드가 동기화 락을 기다리는 상태
- Waiting(대기 상태) : 쓰레드가 무기한으로 다른 쓰레드의 작업을 기다리는 상태
- Timed Waiting(시간 제한 대기 상태) : 쓰레드가 일정 시간 동안 다른 쓰레드의 작업을 기다리는 상태
- Terminated(종료 상태) : 쓰레드 실행이 완료된 상태
[ NEW ]
- 쓰레드가 생성되고 아직 시작되지 않은 사태이다.
start()
메서드를 아직 호출하지 않은 상태이다.
[ Runnable ]
- 쓰레드가 실행될 준비가 된 상태이다.
start()
메서드가 호출되면 NEW → Runnable 상태로 들어간다.- Runnable 상태에 있는 모든 쓰레드가 동시에 실행되는 것이 아니라 운영체제 스케줄러가 각 쓰레드에 CPU 시간을 할당하여 실행하기 때문에 Runnable 상태에 있는 쓰레드는 스케줄러 실행 대기열에 포함되어 있다가 차례로 CPU에서 실행된다.
- 운영체제 스케줄러의 실행 대기열에 있든, CPU에서 실제 실행되고 있든 모두 Runnable 상태로 본다.
[ Blocked ]
- 쓰레드가 다른 쓰레드에 의해 동기화 락을 얻기 위해 기다리는 상태이다.(예시 : 자바의 synchronized 키워드)
- 만약 하나의 쓰레드가 synchronized 블럭에 들어갔다면 나머지 쓰레드들은 lock을 획득하지 못해 대기하는 상태가 되는데 그 상태가 바로 Blocked 이다.
[ Waiting ]
- 쓰레드가 다른 쓰레드의 특정 작업이 완료될 때까지 무기한 기다리는 상태이다.
wait()
,join()
메서드가 호출될 때 이 상태가 된다.
[ Timed Waiting ]
- 쓰레드가 특정 시간동안 다른 쓰레드의 작업이 완료되기를 기다리는 상태이다.
sleep(millis)
,wait(millis)
,join(millis)
메서드가 호출될 때 이 상태가 된다.- 주어진 시간이 경과하거나 다른 쓰레드가 해당 쓰레드를 깨우면 이 상태에서 벗어난다.
[ Terminated ]
- 쓰레드 실행이 완료된 상태이다.
- 쓰레드가 정상적으로 종료되거나 예외가 발생한 경우 이 상태로 들어간다.
- 쓰레드는 한 번 종료되면 다시 시작할 수 없다.
public class ThreadStateMain {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRunnable(), "myThread");
log("thread.state1 = " + thread.getState()); // 1. NEW
log("thread.start");
thread.start();
Thread.sleep(1000);
log("thread.state3 = " + thread.getState()); // 3. TIMED_WAITING
log("sleep end");
Thread.sleep(4000);
log("thread.state5 = " + thread.getState()); // 5. TERMINATED
}
static class MyRunnable implements Runnable {
@Override
public void run() {
try {
log("start");
log("thread.state2 = " + Thread.currentThread().getState()); // 2. RUNNABLE
log("thread.sleep");
Thread.sleep(3000);
log("sleep end");
log("thread.state4 = " + Thread.currentThread().getState()); // 4. RUNNABLE
log("end");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
실행 결과 분석
16:46:53.888 [ main] thread.state1 = NEW
16:46:53.889 [ main] thread.start
16:46:53.890 [ myThread] start
16:46:53.890 [ myThread] thread.state2 = RUNNABLE
16:46:53.890 [ myThread] thread.sleep
16:46:54.895 [ main] thread.state3 = TIMED_WAITING
16:46:54.896 [ main] sleep end
16:46:56.895 [ myThread] sleep end
16:46:56.896 [ myThread] thread.state4 = RUNNABLE
16:46:56.896 [ myThread] end
16:46:58.901 [ main] thread.state5 = TERMINATED
📚 join
public class ThreadSumMain {
public static void main(String[] args) throws InterruptedException {
HelloThread task1 = new HelloThread(1, 50);
HelloThread task2 = new HelloThread(51, 100);
Thread thread1 = new Thread(task1, "thread1");
Thread thread2 = new Thread(task2, "thread2");
thread1.start();
thread2.start();
//thread1.join();
//thread2.join();
thread1.join(1000);
thread2.join(1000);
log("task1.result = " + task1.result);
log("task2.result = " + task2.result);
int sum = task1.result + task2.result;
log("sum = " + sum);
}
static class HelloThread implements Runnable {
int start;
int end;
int result = 0;
public HelloThread(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public void run() {
sleep(2000);
int sum = 0;
for (int i = start; i <= end; i++) {
sum += i;
}
result = sum;
}
}
}
join()
: 다른 쓰레드가 완료될 때까지 무기한 기다린다.join(millis)
: 다른 쓰레드의 작업을 일정 시간 동안만 기다린다.
📚 인터럽트
- 인터럽트를 사용하면 WAITING, TIMED_WAITING 같은 대기 상태 쓰레드를 직접 깨워서 작동하는 Runnable 상태로 만들 수 있다.
public class ThreadStopMain {
public static void main(String[] args) {
MyTask myTask = new MyTask();
Thread thread = new Thread(myTask, "task");
thread.start();
sleep(4000);
thread.interrupt();
log("인터럽트 상태 1 = " + thread.isInterrupted());
}
static class MyTask implements Runnable {
@Override
public void run() {
try {
while (true) {
log("작업 중");
Thread.sleep(3000);
}
} catch (InterruptedException e) {
log("인터럽트 상태 2 = " + Thread.currentThread().isInterrupted());
log("인터럽트 메시지 = " + e.getMessage());
log("상태 = " + Thread.currentThread().getState());
log("자원 정리");
log("작업 종료");
}
}
}
}
- 여기서 MyTask 쓰레드는 작업 후 3초동안 대기 상태로 들어가게 된다.
- main 쓰레드에서 MyTask 쓰레드를 실행한 후 4초 뒤에 인터럽트를 걸게 된다.
- MyTask 쓰레드의 경우 4초 시점에는 대기 상태에 진입한 상태로 main 쓰레드에서 인터럽트를 걸면 대기 상태에서 깨어나 실행 상태로 전환이 된다.
- 쓰레드의 인터럽트 상태를 확인하려면
isInterrupted()
메서드를 사용하면 된다. - 하지만 직접 체크해서 사용할 때는
Thread.interrupted()
메서드를 사용하면 된다. - 쓰레드가 인터럽트 상태라면 true를 반환하고 해당 쓰레드 인터럽트 상태를 false로 변경한다.
- 쓰레드가 인터럽트 상태가 아니라면 false를 반환하고 해당 쓰레드 인터럽트 상태를 변경하지 않는다.
📚 yield
- 다른 쓰레드에게 실행 기회를 양보하고 싶다면
yield()
메서드를 사용한다.