스레드를 생성하고 start()
를 호출하면 바로 스레드가 실행될 것 같지만, 사실 실행 대기(Runnable) 상태가 된다. 실행 대기 상태란 스케줄링이 되지 않아서 실행을 기다리고 있는 상태를 말한다.
실행 대기 상태에 있는 스레드 중에서 스레드 스케줄링으로 선택된 스레드가 CPU를 점유하고 run()
메소드를 실행한다. 이때를 실행(Running) 상태라고 한다.
실행 상태의 스레드는 run()
메서드를 실행 완료하기 전에 스레드 스케줄링(time slice)에 의해 다시 실행 대기 상태로 돌아갈 수 있다. 그리고 또 다른 스레드가 스케줄링에 의해 실행 상태가 된다. 이렇게 번갈아가며 여러 스레드가 자신의 run()
를 조금씩 실행한다. 실행 상태에서 run()
가 종료되면 스레드의 실행도 멈추게 된다. 이 상태를 종료 상태(Terminated)라 한다.
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
Calc calc = new Calc();
User1 user1 = new User1();
user1.setCalc(calc);
user1.start(); // user1
User2 user2 = new User2();
user2.setCalc(calc);
user2.start(); // user2
System.out.println(calc.getMemory()); // main
User1 :100 // user1
100 // main
User2 :50 // user2
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
Calc calc = new Calc();
User1 user1 = new User1();
user1.setCalc(calc);
user1.start(); // user1
User2 user2 = new User2();
user2.setCalc(calc);
user2.start(); // user2
System.out.println(calc.getMemory()); // main
**mainThread.sleep(1000);
System.out.println(calc.getMemory());
}**
User1 :100 // user1
100 // main
User2 :50 // user2
50 // main
보는 것처럼 시간을 조금 두고 다시 calc.getMemory()
를 해보면 값이 변해있다는 걸 확인할 수 있다. 즉 start()
를 호출한다고 해서 바로 스레드가 실행되는 것이 아니라 스레드 스케줄링에 의해서 적절한 시점에 시작되며, 시작하는 시간까지의 차이가 있다는 것이다.
스레드의 생명주기는 생성 → 실행대기 → 실행 → 실행 종료이다. 스레드가 실행 대기 상태일 때는 위와 같이 세 가지 상태가 있다.
스레드의 상태를 코드로 확인하고 싶다면 Thread의 getState()
를 호출하면 된다. enum 상수 값으로 스레드의 상태를 리턴한다. 각 값이 의미하는 바는 다음과 같다.
Thread.State.NEW
: 스레드 객체가 생성Thread.State.RUNNABLE
: 실행 대기. 언제든 실행상태가 될 수 있음Thread.State.WAITING
: 다른 스레드가 통지할 때까지 기다리는 상태Thread.State.TIMED_WAITING
: 주어진 시간 동안 기다리는 상태Thread.State.BLOCKED
: 사용하고자 하는 객체의 락이 풀릴 때까지 기다리는 상태Thread.State.TERMINATED
: 실행을 마친 상태스레드의 상태와 관련해서, 아래의 코드를 보자.
public class StatePrintThread extends Thread {
private Thread target;
private Thread target2;
public StatePrintThread(Thread target, Thread target2){
this.target = target;
this.target2 = target2;
}
@Override
public void run() {
while(true){
Thread.State state = target.getState();
Thread.State state2 = target2.getState();
System.out.println("target Thread Name: " +target.getName());
System.out.println("target2 Thread Name: " +target2.getName());
System.out.println("current Thread Name: " +Thread.currentThread().getName());
System.out.println("thread status : " + state);
System.out.println("thread2 status : " + state2);
if(state == Thread.State.NEW){
target.start();
}
if(state == Thread.State.TERMINATED){
break;
}
try{
Thread.sleep(500);
} catch (Exception e){}
}
}
}
public class Target extends Thread {
@Override
public void run() {
System.out.println("here we go");
Thread thread = Thread.currentThread();
thread.setName("target");
System.out.println("thread name: " +thread.getName());
System.out.println("thread group: " +this.getThreadGroup());
for(int i = 0; i<100000000; i++){}
try{
Thread.sleep(1500);
} catch (Exception e){}
for(int i = 0; i<100000000; i++){}
}
}
public class ThreadStatusApp {
public static void main(String[] args) {
StatePrintThread printThread = new StatePrintThread(new Target(), new TestThread());
printThread.start();
}
}
target Thread Name: Thread-0
target2 Thread Name: Thread-1
current Thread Name: Thread-2
thread status : NEW
thread2 status : NEW
here we go
thread name: target
thread group: java.lang.ThreadGroup[name=main,maxpri=10]
target Thread Name: target
target2 Thread Name: Thread-1
current Thread Name: Thread-2
thread status : TIMED_WAITING
thread2 status : NEW
target Thread Name: target
target2 Thread Name: Thread-1
current Thread Name: Thread-2
thread status : TIMED_WAITING
thread2 status : NEW
target Thread Name: target
target2 Thread Name: Thread-1
current Thread Name: Thread-2
thread status : TERMINATED
thread2 status : NEW
StatePrintThread가 while문을 돌리는 동안 Target 에서는 1억번 for 루핑, 1.5초 멈추었다가 다시 1억번 for 루핑이 일어난다. 처음 target 스레드가 만들어졌을 때에는 상태가 NEW 이고, 루핑을 통과하면서 일시정지 상태가 되어서 이를 StatePrintThread가 출력하고, 다시 target이 모든 루핑을 통과하여 run() 실행이 끝나 TERMINATED 상태가 되고, 이를 printThread가 출력하고 while 루핑에서 break하고 있다.
Runnable.run()
vs. Thread.start()
두 메서드의 가장 큰 차이점은 run()
은 메서드가 호출되고 있는 현재 스레드에서 코드를 수행한다는 점이다. 반면 start()
가 호출되면 새 스레드가 생기고 그 스레드에서 run()
이 수행된다. start()
같은 경우 해당 스레드가 실행되고 있는지 상태를 체크한 뒤에 스레드를 생성하기 때문에 같은 스레드에서 두 번 start()
를 호출하면 IllegalThreadStateException()
이 발생한다. 반면 run()
은 코드만 수행하기 때문에 여러 번 호출해도 상관은 없지만, 새로운 스레드가 생성되지 않기 때문에 run()
을 직접 사용할 이유가 없다.
reference:
「이것이 자바다」, 신용권