Thread 클래스는 스레드를 생성하고 관리하는 기능
Thread 클래스가 제공하는 정보
public class ThreadInfoMain {
public static void main(String[] args) {
//main 스레드 -> 처음 딱 실행 되는게 main Thread
Thread mainThread = Thread.currentThread();
log("main Thread: " + mainThread);
log("mainThreadId: " + mainThread.getId()); //Java가 자동으로 만들어줌
log("mainThreadName: " + mainThread.getName());
log("mainThread.getPriority: " + mainThread.getPriority()); // 우선순위 기본이 5
log("mainThread.getThreadGroup: " + mainThread.getThreadGroup());
log("mainThread.getState: " + mainThread.getState());
Thread myThread = new Thread(new HelloRunnable(), "myThread");
log("myThread: " + myThread);
log("myThreadId: " + myThread.getId()); //Java가 자동으로 만들어줌
log("myThreadName: " + myThread.getName());
log("myThread.getPriority: " + myThread.getPriority()); // 우선순위 기본이 5
log("myThread.getThreadGroup: " + myThread.getThreadGroup());
log("myThread.getState: " + myThread.getState());
}
}
Thread myThread = new Thread(new HelloRunnable(), "myThread");
log("myThread = " + myThread);
myThread 객체를 문자열로 변환하여 출력, Thread 클래스의 toString() 메서드 스레드 ID, 스레드 이름, 우선순위, 스레드 그룹 포함하는 문자열
log("myThread.threadId() = " + myThread.threadId());
threadId() : 스레드의 고유 식별자를 반환하는 메서드
log("myThread.getName() = " + myThread.getName());
getName(): 스레드 이름을 반환하는 메서드. 참고로 스레드 ID는 중복되지 않지만, 스레드 이름은 중복될 수 있다.
log("myThread.getPriority() = " + myThread.getPriority());
getPriority(): 우선순위를 변환하는 메서드. 기본 값은 5, 우선순위는 가장 낮음 1 ~ 10 까지 지정 가능. setPriority()를 사용해서 우선순위 변경 가능
log("myThread.getThreadGroup() = " + myThread.getThreadGroup());
getThreadGroup(): 스레드가 속한 스레드 그룹을 반환하는 메서드, 스레드 그룹은 스레드를 그룹화하여 관리 할 수 있는 기능 제공. 기본적으로 모든 스레드는 부모 스레드와 동일한 스레드 그룹에 속한다.
스레드 그룹은 여러 스레드를 하나의 그룹으로 묶어서 특정 작업을 수행 할 수 있다.
부모 스레드(Parent Thread): 새로운 스레드를 생성하는 스레드를 의미, 스레드는 기본적으로 다른 스레드 에 의해 생성된다. 이러한 생성 관계에서 새로 생성된 스레드는 생성한 스레드를 부모로 간주한다.
myThread는 main 스레드에 의해 생성되었으므로, main 스레드가 부모 스레드이다.
myThread 도 부모 스레드인 main 스레드의 그룹인 main 스레드 그룹에 소속된다
log("myThread.getState() = " + myThread.getState());
getState(): 현재 상태를 반환하는 메서드

스레드 상태
자바 스레드(Thread)의 생명 주기는 여러 상태(state)로 나뉘어지며, 각 상태는 스레드가 실행되고 종료되기까지의 과정을 나타낸다.
New
스레드가 생성되고 아직 시작되지 않은 상태
이 상태는 스레드 객체가 생성되지만, start 메서드가 호출 되지 않은 상태
Runnable
스레드가 실행될 준비가 된 상태이다. 이 상태에서 스레드는 실제로 CPU에서 실행
start() 메서드가 호출되면 스레드는 이 상태로 들어간다.
Runnable 상태에 있는 모든 스레드가 동시에 실행되는 것은 아니다. 운영체제의 스케줄러가 각 스레드 에 CPU 시간을 할당하여 실행하기 때문에, Runnable 상태에 있는 스레드는 스케줄러의 실행 대기열에 포 함되어 있다가 차례로 CPU에서 실행.
Blocked (차단 상태)
스레드가 다른 스레드에 의해 동기화 락을 얻기 위해 기다리는 상태
synchronized 블록에 진입하기 위해 락을 얻어야 하는 경우 이 상태에 들어간다.
Waiting (대기 상태)
스레드가 다른 스레드의 특정 작업이 완료되기를 무기한 기다리는 상태
wait(), join() 메서드가 호출
Timed Waiting (시간 제한 대기 상태)
스레드가 특정 시간 동안 다른 스레드의 작업이 완료되기를 기다리는 상태
sleep(long millis) , wait(long timeout) , join(long millis) 메서드가 호출
주어진 시간이 경과하거나 다른 스레드가 해당 스레드를 깨우면 이 상태에서 벗어난다.
Terminated (종료 상태)
스레드의 실행이 완료된 상태이다.
스레드가 정상적으로 종료되거나, 예외가 발생하여 종료된 경우 이 상태로 들어간다.
자바 스레드의 상태 전이 과정
1. New -> Runnable : start() 메서드를 호출하면 스레드가 Runnable 상태
2. Runnable → Blocked/Waiting/Timed Waiting : 스레드가 락을 얻지 못하거나, wait() 또는
sleep() 메서드를 호출할 때 해당 상태로 전이
3. Blocked/Waiting/Timed Waiting → Runnable: 스레드가 락을 얻거나, 기다림이 완료되면 다시 Runnable 상태로 돌아간다.
4. Runnable → Terminated : 스레드의 run() 메서드가 완료되면 스레드는 Terminated 상태
public class ThreadStateMain {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRunnable(), "myThread");
log("myThread.state1 : " + thread.getState()); //new
log("myThread.start()");
thread.start();
Thread.sleep(1000);
log("myThread.state3 : " + thread.getState()); //바로 찍으면 Runnable 에서 sleep이 적용 x
Thread.sleep(4000);
log("myThread.state4 : " + thread.getState()); // 종료된 상태
log("end");
}
//myThread가 만들어지면 MyRunnable이 작동한다.
static class MyRunnable implements Runnable {
public void run() {
try {
log("start");
log("myThread state2 = " + Thread.currentThread().getState()); //Runnable
log("sleep() start");
Thread.sleep(3000); // 자는중 -> Thread 상태 찍을 수 없음
log("myThread sleep() end");
log("myThread state4 = " + Thread.currentThread().getState());
log("end");
} catch (InterruptedException e) { //Run에서는 예외를 던질 수 없다.
throw new RuntimeException(e);
}
}
}
}


체크 예외
부모 메서드가 체크 예외를 던지지 않는 경우, 재정의된 자식 메서드도 체크 예외를 던질 수 없다.
자식 메서드는 부모 메서드가 던질 수 있는 체크 예외의 하위 타입만 던질 수 있다.
언체크(런타임) 예외
예외 처리를 강제하지 않으므로 상관없이 던질 수 있다.
public class CheckedExceptionMain {
public static void main(String[] args) throws Exception { // main은 밖으로 던질 수 있다
//throw new Exception();
}
static class CheckedRunnable implements Runnable { // run은 체크 예외를 밖으로 던질 수 없다.
@Override
public void run() {
//throw new Exception();
}
}
}
자바는 왜 이런 제약을 두었는가???
부모 클래스의 메서드를 호출하는 클라이언트 코드는 부모 메서드가 던지는 특정 예외만 처리하도록 작성
자식 클래스가 더 넓은 범위의 예외를 던지면 해당 코드는 모든 예외를 처리하지 못 할 수도 있다.
체크 예외 재정의 규칙
자식 클래스에 재정의된 메서드는 부모 메서드가 던질 수 있는 체크 예외의 하위 타입만을 던질 수 있다. 원래 메서드가 체크 예외를 던지지 않는 경우, 재정의된 메서드도 체크 예외를 던질 수 없다.
//따로 생성 할 수 없게 적용
public abstract class ThreadUtils {
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
log("인터럽트 발생 : " + e.getMessage());
throw new RuntimeException(e);
}
}
}
join() 메서드를 통해 WAITING (대기 상태)가 무엇이고 왜 필요한지 알아보자.
public class JoinMainV0 {
public static void main(String[] args) {
log("start");
Thread thread1 = new Thread(new Job(), "thread - 1");
Thread thread2 = new Thread(new Job(), "thread - 2");
thread1.start();
thread2.start();
log("end"); //main은 기다리지 않는다.
}
static class Job implements Runnable {
public void run() {
log("작업 시작");
sleep(2000);
log("작업 완료");
}
}
}

main 스레드는 thread -1,2를 기다리지 않고, 자신의 코드 실행.
즉 main 스레드는 기다리지 않는다는 점!
main 스레드를 가장 마지막에 종료하려면 어떻게 해야할까?
public class JoinMainV2 {
public static void main(String[] args) {
log("start");
SumTask task1 = new SumTask(1, 50);
SumTask task2 = new SumTask(51, 100);
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
thread1.start();
thread2.start();
//기다리게 하는 방법 = sleep
//정확한 타이밍 맞추기 어려움
log("mainThread sleep");
sleep(3000);
log("mainThread wakeup");
log("task1.rst = " + task1.rstVal);
log("task2.rst = " + task2.rstVal);
int sumAll = task1.rstVal + task2.rstVal;
log("sumAll = " + sumAll);
log("end");
}
static class SumTask implements Runnable {
int statVal;
int endVal;
int rstVal;
public SumTask(int statVal, int endVal) {
this.statVal = statVal;
this.endVal = endVal;
}
@Override
public void run() {
log("작업 시작");
sleep(2000);
int sum = 0;
for (int i = statVal; i <= endVal ; i++) {
sum += i;
}
rstVal = sum;
log("작업 완료 = " + rstVal);
}
}
}
task1.result , task2.result 모두 0


main 스레드는 thread-1 , thread-2 를 생성하고 start() 로 실행
thread-1은 x001 인스턴스의 run() 메서드를 실행한다. thread-2는 x002 인스턴스의 run() 메서드를 실행.

main 스레드가 실행한 start() 메서드는 스레드의 실행이 끝날 때 까지 기다리 지 않는다! 다른 스레드를 실행만 해두고, 자신의 다음 코드를 실행할 뿐

이미 main 스레드는 종료된 상태임..
public class JoinMainV3 {
public static void main(String[] args) throws InterruptedException {
log("start");
SumTask task1 = new SumTask(1, 50);
SumTask task2 = new SumTask(51, 100);
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
thread1.start();
thread2.start();
//기다리게 하는 방법 = sleep
log("join() -> main 스레드가 thread1, thread2 종료까지 대기");
thread1.join();
thread2.join();
log("mainThread 대기 완료");
log("task1.rst = " + task1.rstVal);
log("task2.rst = " + task2.rstVal);
int sumAll = task1.rstVal + task2.rstVal;
log("sumAll = " + sumAll);
log("end");
}
static class SumTask implements Runnable {
int statVal;
int endVal;
int rstVal;
public SumTask(int statVal, int endVal) {
this.statVal = statVal;
this.endVal = endVal;
}
@Override
public void run() {
log("작업 시작");
sleep(2000);
int sum = 0;
for (int i = statVal; i <= endVal ; i++) {
sum += i;
}
rstVal = sum;
log("작업 완료 = " + rstVal);
}
}
}
sleep을 사용하더라도 정확히 맞아 떨어지지는 않는다

public class JoinMainV4 {
public static void main(String[] args) throws InterruptedException {
log("start");
SumTask task1 = new SumTask(1, 50);
Thread thread = new Thread(task1, "thread -1");
thread.start();
//기다리게 하는 방법 = sleep
log("join(1000) -> main 스레드가 thread1, thread2 종료까지 대기");
thread.join(1000);
log("mainThread 대기 완료");
log("task1.rst = " + task1.rstVal);
}
static class SumTask implements Runnable {
int statVal;
int endVal;
int rstVal;
public SumTask(int statVal, int endVal) {
this.statVal = statVal;
this.endVal = endVal;
}
@Override
public void run() {
log("작업 시작");
sleep(2000);
int sum = 0;
for (int i = statVal; i <= endVal ; i++) {
sum += i;
}
rstVal = sum;
log("작업 완료 = " + rstVal);
}
}
}

main 스레드는 thread-1,2가 종료 되기 전까지 기다린다.
main 스레드는 thread1.join() 코드 안에서 더는 진행하지 않고 멈추어 기다린다. 이후에 thread-1 이 종료되면 main 스레드는 RUNNABLE 상태가 되고 다음 코드로 이동 / Thread -2도 마찮가지
join()의 단점은 다른 스레드가 완료 될 때까지 무기한 기다리는 단점이 있음
별도의 스레드에서 1 ~ 50까지 더하고, 그 결과를 조회한다.
join(1000) 을 사용해서 1초만 대기한다.

main 스레드는 join(1000) 을 사용해서 thread-1을 1초간 기다린다.
이때 main 스레드의 상태는 WAITING이 아니라 TIMED_WAITING이 된다.
보통 무기한 대기하면 WAITING 상태가 되고, 특정 시간 만큼만 대기하는 경우 TIMED_WAITING 상태가 된다.
초가 지나도 thread-1의 작업이 완료되지 않으므로, main main 스레드가 종료된 이후에 thread-1이 계산을 끝낸다. 따라서 작업 완료 result = 1275이 출력된다. 스레드는 대기를 중단