import static thread.util.Logger.log;
public class ThreadInfo {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
log("mainThread : " + mainThread); // 쓰레드 객체 정보
log("mainThread.threadId() : " + mainThread.threadId()); // 쓰레드 ID
log("mainThread.getName() : " + mainThread.getName()); // 쓰레드 이름
log("mainThread.getPriority() : " + mainThread.getPriority()); // 쓰레드 우선순위
log("mainThread.getThreadGroup() : " + mainThread.getThreadGroup()); // 쓰레드 쓰레드 그룹
log("mainThread.getState() : " + mainThread.getState()); // 쓰레드 상태
}
}
실행 결과
11:50:30.134 [ main] mainThread : Thread[#1,main,5,main]
11:50:30.145 [ main] mainThread.threadId() : 1
11:50:30.146 [ main] mainThread.getName() : main
11:50:30.152 [ main] mainThread.getPriority() : 5
11:50:30.152 [ main] mainThread.getThreadGroup() : java.lang.ThreadGroup[name=main,maxpri=10]
11:50:30.153 [ main] mainThread.getState() : RUNNABLE
쓰레드 정보 설명
start()
메서드가 호출되지 않은 상태start()
메서드가 호출되면 쓰레드는 이 상태로 들어간다.wait()
, join()
메서드가 호출될 때 이 상태가 된다.sleep(long millis)
, wait(long timeout)
, join(long millis)
메서드가 호출될 때 이 상태가 된다.import static thread.util.Logger.log;
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRunnable(), "myThread");
log("myThread.getState1() : " + thread.getState());
thread.start();
Thread.sleep(1000);
log("myThread.getState3() : " + thread.getState());
Thread.sleep(4000);
log("myThread.getState5() : " + thread.getState());
}
static class MyRunnable implements Runnable {
@Override
public void run() {
try {
log("start");
log("myThread.getState2() : " + Thread.currentThread().getState());
log("sleep() start");
Thread.sleep(3000);
log("sleep() end");
log("myThread.getState4() : " + Thread.currentThread().getState());
log("end");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
실행 결과
14:07:51.401 [ main] RUNNABLE
14:07:51.416 [ main] thread.getState1() : NEW
14:07:51.417 [ main] RUNNABLE
14:07:51.417 [ myThread] start
14:07:51.418 [ myThread] myThread.getState2() : RUNNABLE
14:07:51.418 [ myThread] sleep() start
14:07:52.429 [ main] myThread.getState3() : TIMED_WAITING
14:07:54.424 [ myThread] sleep() end
14:07:54.425 [ myThread] myThread.getState4() : RUNNABLE
14:07:54.426 [ myThread] end
14:07:56.430 [ main] myThread.getState5() : TERMINATED
Runnable
인터페이스의 원형은 아래와 같다.
@FunctionalInterface
public interface Runnable {
void run();
}
쓰레드를 생성할 때 이 Runnable
인터페이스를 구현하여 만든다.
자바에서 메서드 오버라이딩을 할 때, 재정의 메서드가 지켜야할 예외와 관련된 규칙이 있다.
throw new Exception()
이 컴파일되지 않는 것main
은 체크 예외를 밖으로 던지는 것이 가능하나, run
은 체크 예외를 밖으로 던질 수 없다.import static thread.util.Logger.log;
import static thread.info.ThreadUtils.sleep;
public class JoinMainV1 {
public static void main(String[] args) {
SumTask task1 = new SumTask(1, 50);
SumTask task2 = new SumTask(51, 100);
Thread thread1 = new Thread(task1, "thread-1");
Thread thread2 = new Thread(task2, "thread-2");
thread1.start();
thread2.start();
log(task1.totalValue);
log(task2.totalValue);
}
static class SumTask implements Runnable {
int startValue;
int endValue;
int totalValue;
public SumTask(int startValue, int endValue) {
this.startValue = startValue;
this.endValue = endValue;
}
@Override
public void run() {
log("작업 시작");
sleep(2000); // 대략 2초 걸린다고 가정
int sum = 0;
for (int i = startValue; i <= endValue; i++) {
sum += i;
}
totalValue = sum;
log("작업 완료");
}
}
}
실행 결과
15:17:13.446 [ thread-1] 작업 시작
15:17:13.446 [ main] 0
15:17:13.446 [ thread-2] 작업 시작
15:17:13.450 [ main] 0
15:17:15.478 [ thread-2] 작업 완료
15:17:15.478 [ thread-1] 작업 완료
👉
main
쓰레드에서 두 쓰레드(task1
,task2
)를 생성하고 두 쓰레드에서 연산을 진행하면서 원하는 대로 결과를 출력할 수 있을거라고 생각했으나 결과를 보니 두 쓰레드의 연산 결과가 0이 나오는 문제가 발생한다.왜냐하면 쓰레드의 연산에는 대략 2초가 걸린다고 가정했기에 연산의 결과가 저장되지 않은 상태에서
main
쓰레드에서 바로 결과를 조회하려고 했기에 결과가 0이 나오게 되는 것이다.인스턴스 메서드를 호출하면 어떤 인스턴스의 메서드를 호출했는지 기억하기 위해 해당 인스턴스 참조값을 스택 프레임에 저장한다. 이것이 바로
this
이다.특정 메서드 안에서
this
를 호출하면 스택 프레임에 저장된this
값을 불러서 사용한다.정리하면 이
this
는 호출된 인스턴스 메서드가 소속된 객체의 참조값을 가리키며 이것이 스택 프레임 내부에 저장되어 있는 것이다.
간단하다. 현재 연산을 담당하는 쓰레드의 경우 작업 시간은 대략 2초가 걸리므로 main
쓰레드에서 3초 정도만 기다려주면 연산 쓰레드의 결과를 확인할 수 있게 된다.
import static thread.util.Logger.log;
import static thread.info.ThreadUtils.sleep;
public class JoinMainV1 {
public static void main(String[] args) {
SumTask task1 = new SumTask(1, 50);
SumTask task2 = new SumTask(51, 100);
Thread thread1 = new Thread(task1, "thread-1");
Thread thread2 = new Thread(task2, "thread-2");
thread1.start();
thread2.start();
sleep(3000); // main 쓰레드에서 기다리기
log(task1.totalValue);
log(task2.totalValue);
}
static class SumTask implements Runnable {
int startValue;
int endValue;
int totalValue;
public SumTask(int startValue, int endValue) {
this.startValue = startValue;
this.endValue = endValue;
}
@Override
public void run() {
log("작업 시작");
sleep(2000); // 대략 2초 걸린다고 가정
int sum = 0;
for (int i = startValue; i <= endValue; i++) {
sum += i;
}
totalValue = sum;
log("작업 완료");
}
}
}
실행 결과
15:37:46.315 [ thread-1] 작업 시작
15:37:46.315 [ thread-2] 작업 시작
15:37:48.334 [ thread-2] 작업 완료
15:37:48.334 [ thread-1] 작업 완료
15:37:49.287 [ main] 1275
15:37:49.287 [ main] 3775
👉위와 같이
sleep()
을 사용하면 해결이 가능하나 무작정 기다리는 방법은 여러 손해가 발생할 수 있고 연산 담당 쓰레드의 수행 시간이 달라지는 경우 정확한 타이밍을 예측하기 어렵다.
join()
메서드를 사용하면 깔끔하게 문제를 해결할 수 있다.
import static thread.info.ThreadUtils.sleep;
import static thread.util.Logger.log;
public class JoinMainV2 {
public static void main(String[] args) throws InterruptedException {
SumTask task1 = new SumTask(1, 50);
SumTask task2 = new SumTask(51, 100);
Thread thread1 = new Thread(task1, "thread-1");
Thread thread2 = new Thread(task2, "thread-2");
thread1.start();
thread2.start();
// 쓰레드가 종료될 때까지 대기
thread1.join();
thread2.join();
log(task1.totalValue);
log(task2.totalValue);
}
static class SumTask implements Runnable {
int startValue;
int endValue;
int totalValue;
public SumTask(int startValue, int endValue) {
this.startValue = startValue;
this.endValue = endValue;
}
@Override
public void run() {
log("작업 시작");
sleep(2000); // 대략 2초 걸린다고 가정
int sum = 0;
for (int i = startValue; i <= endValue; i++) {
sum += i;
}
totalValue = sum;
log("작업 완료");
}
}
}
실행 결과
15:43:16.891 [ thread-2] 작업 시작
15:43:16.891 [ thread-1] 작업 시작
15:43:18.901 [ thread-1] 작업 완료
15:43:18.901 [ thread-2] 작업 완료
15:43:18.902 [ main] 1275
15:43:18.902 [ main] 3775
main
쓰레드에서 아래 코드를 실행하면 thread1
, thread2
가 끝날 때까지 기다린다. 이 때, main
쓰레드는 WAITING 상태가 된다. thread1
, thread2
가 끝나면 main
쓰레드의 상태는 다시 RUNNABLE이 된다.
WAITING(대기 상태)
join()
을 호출하는 쓰레드는 대상 쓰레드가 TERMINATED 상태가 될 때까지 대기한다. 대상 쓰레드가 TERMINATED 상태가 되면 join()
을 호출하는 쓰레드는 다시 RUNNABLE 상태가 된다.하지만 join()
의 단점은 다른 쓰레드가 완료될 때까지 무기한 기다려야 한다는 것이다. 다른 쓰레드의 작업을 일정 시간 동안만 기다리고 싶다면 join()
내부 파라미터로 특정 시간을 지정해주면 된다.
thread1.join();
thread2.join();
join()
은 두 가지 메서드가 있다.
join()
: 호출 쓰레드는 대상 쓰레드가 완료될 때까지 무기한 기다린다.join(ms)
: 호출 쓰레드는 특정 시간 만큼만 대기한다. 호출 쓰레드는 지정한 시간이 지나면 다시 RUNNABLE 상태가 되면서 다음 라인의 코드를 수행한다.import static thread.info.ThreadUtils.sleep;
import static thread.util.Logger.log;
public class JoinMainV3 {
public static void main(String[] args) throws InterruptedException {
SumTask task1 = new SumTask(1, 50);
Thread thread1 = new Thread(task1, "thread-1");
thread1.start();
// 쓰레드가 종료될 때까지 대기
log("join(1000) - main 스레드가 thread1 종료까지 1초 대기");
thread1.join(1000);
log("main 쓰레드 대기 완료");
log("task1.totalValue : " + task1.totalValue);
}
static class SumTask implements Runnable {
int startValue;
int endValue;
int totalValue;
public SumTask(int startValue, int endValue) {
this.startValue = startValue;
this.endValue = endValue;
}
@Override
public void run() {
log("작업 시작");
sleep(2000); // 대략 2초 걸린다고 가정
int sum = 0;
for (int i = startValue; i <= endValue; i++) {
sum += i;
}
totalValue = sum;
log("작업 완료 totalValue : " + totalValue);
}
}
}
실행 결과
16:00:43.922 [ thread-1] 작업 시작
16:00:43.922 [ main] join(1000) - main 스레드가 thread1 종료까지 1초 대기
16:00:44.934 [ main] main 쓰레드 대기 완료
16:00:44.940 [ main] task1.totalValue : 0
16:00:45.933 [ thread-1] 작업 완료 totalValue : 1275
main
쓰레드에서 join(1000)
을 사용해서 연산 쓰레드를 1초간 기다린다.join(1000)
을 호출한 후 main
쓰레드의 상태는 RUNNABLE → TIMED_WAITING이 된다.main
쓰레드에서 기다리는 시간은 1초로 아직 연산 쓰레드에서 연산 수행이 완료가 되지 않은 상태이다. 1초가 지나면 main
쓰레드의 상태는 TIMED_WAITING → RUNNABLE이 된다.main
쓰레드의 대기가 완료되었다는 부분 이후 log("task1.totalValue : " + task1.totalValue);
코드 라인을 실행하게 되면 아직 연산이 완료되지 않아 0이 터미널에 출력된다.main
쓰레드가 종료된 이후 연산 쓰레드가 종료되므로 연산 쓰레드가 종료되면서 log("작업 완료 totalValue : " + totalValue);
코드 라인이 실행되면서 결괏값이 터미널에 출력된다.총 실행 시간이 얼마나 소요될지 예측해본다.
import static thread.info.ThreadUtils.sleep;
import static thread.util.Logger.log;
public class JoinTest1Main {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new MyTask(), "thread1");
Thread thread2 = new Thread(new MyTask(), "thread2");
Thread thread3 = new Thread(new MyTask(), "thread3");
thread1.start();
thread1.join();
thread2.start();
thread2.join();
thread3.start();
thread3.join();
log("모든 쓰레드 실행 완료");
}
static class MyTask implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
log(i);
sleep(1000);
}
}
}
}
위의 코드 총 소요시간은 각 쓰레드당 걸리는 시간(3초) x 쓰레드 3개로 총 9초의 시간이 소요된다. 이를 병렬 처리하여 3초로 단축한다.
import static thread.info.ThreadUtils.sleep;
import static thread.util.Logger.log;
public class JoinTest1Main {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new MyTask(), "thread1");
Thread thread2 = new Thread(new MyTask(), "thread2");
Thread thread3 = new Thread(new MyTask(), "thread3");
// 쓰레드 3개를 병렬로 처리(9초 → 3초)
thread1.start();
thread2.start();
thread3.start();
thread1.join();
thread2.join();
thread3.join();
log("모든 쓰레드 실행 완료");
}
static class MyTask implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
log(i);
sleep(1000);
}
}
}
}