오늘 겪은 문제
항해 측에서 제공해 준 강의로 자바 스레드를 학습했는데 강의에서 콘솔에 찍히는 결과와 내 인텔리제이 콘솔에 찍히는 결과가 달랐다.
<실행 코드>
public class Main {
public static void main(String[] args) {
Runnable task = () -> {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task : " + Thread.currentThread().getName());
};
Thread thread = new Thread(task, "Thread");
thread.start();
thread.interrupt();
System.out.println("thread.isInterrupted() = " + thread.isInterrupted());
}
}
원인을 추적하기 위해 몇 가지 가정을 하면서 다양한 시도를 해봤다.
시도한 것
가정 1) 나는 main thread가 out 필드를 먼저 점유하는데 반해 강사님은 task thread 가 먼저 점유
시도) main thread 가 out 필드 점유하기 전에 일시 정지로 만들어 task thread 가 점유할 수 있도록 하기
System 클래스의 out 필드가 static 필드이기에 여러 스레드가 공유하는 자원이라고 생각했고 멀티스레드 환경에서 이 자원을 어떤 스레드가 먼저 점유하느냐에 따라 출력 결과의 순서가 달라질 수 있겠다고 생각했다.
그래서 main thread 가 out 필드를 점유하기 전 일시 정지 상태로 만들어 task thread 가 out 필드를 먼저 점유할 수 있도록 만들었다.
thread.interrupt();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread.isInterrupted() = " + thread.isInterrupted());
그랬더니 의도한 대로 출력 결과의 순서는 강사님의 출력 결과 순서와 일치시킬 수 있었으나 thread.isInterrupted() 의 결과가 false 가 나왔다.(이 부분은 알게된 점에서 다루겠다)
가정 2) e.printStackTrace()는 그 후에 System.out.println()이 있으면 원래 나중에 실행된다.
시도) 싱글스레드 환경에서 Exception 을 발생시키고 e.printStackTrace() 를 catch 블록에서 실행한 후 바깥에서 System.out.println() 실행
public class Test {
public static void main(String[] args) {
try {
String apple = "사과";
Integer appleToInteger = Integer.pareseInt(apple);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Exception 이 발생했습니다.");
}
}
가정했던 것과는 달리 정상적으로 e.printStackTrace() 의 결과가 먼저 콘솔에 나왔다.
가정 3) main thread 가 out 필드를 점유한 상황에서 task thread 역시 출력을 하려하니 어떤 충돌이 발생했다.
시도) main thread 에서 System.out.println() 을 주석처리하여 task thread 만 자원 사용하게 하기
thread.interrupt();
// System.out.println("thread.isInterrupted() = " + thread.isInterrupted());
그랬더니 정상적으로 e.printStackTrace() 결과가 먼저 콘솔에 출력되었다. 이 결과로 우선 출력에 있어 어떠한 충돌이 있다고 결론을 내렸고 printStackTrace() 메서드에서 그 충돌이 발생하지 않을까라는 생각이 들었다.
가정 4) printStackTrace() 메서드의 내부 동작에서 main thread 의 System.out.println() 과 충돌을 일으킨다.
시도) catch 블록에서 printStackTrace() 메서드 대신 getMessage() 메서드를 출력하도록 함
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
System.out.println("task : " + Thread.currentThread().getName());
그랬더니 catch 블록 내부에 있는 출력이 먼저 진행되도록 결과가 잘 출력됐다.
여러 시도를 통해서 결국 main thread 의 System.out.println() 과 task thread 의 e.printStackTrace()가 동시에 이뤄지려는 과정에서 문제가 나타난다고 결론을 내릴 수 있었다.
마지막 확인을 위해서 e.printStackTrace()에 BreakPoint를 걸어 해당 코드 실행 전 main thread 가 System.out.println() 와 관련해 모든 자원을 반납했다는 것을 보장하여 디버깅을 해보았다.
역시나 catch {} 내부의 e.printStackTrace() 가 먼저 잘 나온 것을 볼 수 있다.
해결 방법
매니저님께 질문을 하면서 결국 이 문제에 대한 해답을 어느정도 찾았다.
콘솔에 출력될 String 들은 JVM 에 의해서 일단 메모리 버퍼에 저장된 후 화면에 출력된다.
그런데 out 과 err 는 서로 다른 버퍼를 사용하고 버퍼를 다루는 환경에 따라서 출력 결과가 달라질 수 있는 것이다.
마찬가지로 e.printStackTrace() 도 System.out 과는 다른 버퍼를 사용한다.
인텔리제이에서는 두 개의 버퍼를 서로 다른 두 개의 스레드에서 관리하는데 아마 두 스레드 사이에 우선순위가 존재함으로서 이런 결과가 나오게 되는 것 같다.
알게된 점
원인에 대해서 제대로 알아내지는 못 했지만 이것저것 시도하면서 알게 된 사실들이 있다.
1. interrupt flag 초기화 차이
가정 1번에 대한 시도 결과에서 thread.isInterrupted() = false 가 나왔던 이유를 조사해보니 이 역시 JDK 버전 차이에 의한 것이었다.
JDK 8 에서는 InterruptedException 이 발생하면 interrupt flag 를 false 로 초기화 하는데 반해 JDK 17 에서는 false 로 초기화 하지 않는다.
그로 인해서 thread.isInterrupted() 의 결과를 출력하기 전에 InterruptedException 이 발생한 경우 JDK 8 에서는 false가 JDK 17 에서는 true 가 나온 것이다.
2. 디버깅 옵션 Suspend : All vs Thread
여러가지 시도 중 디버깅을 통해서 확인해보려는 시도도 있었는데 이 때 Suspend 옵션을 All 로 선택하느냐 Thread 로 선택하느냐에도 차이가 있다는 것을 알게 됐다.
All 로 선택하여 디버깅을 하면 해당 breakpoint 에서 멈췄을 때 breakpoint 를 걸어둔 스레드를 제외한 모든 스레드가 일시 중지 상태로 들어간다.(All : 모든 스레드를 중단할 것인가?)
반면 Thread 옵션을 선택해서 디버깅을 하면 해당 breakpoint 에서 멈추더라도 다른 스레드들은 모두 정상적으로 실행이 된다.(Thread : breakpoint 가 걸린 스레드만 중단할 것인가?)
아래 사진은 글 초기의 실행 코드에서 thread.interrupt() 코드 라인에 breakpoint 를 걸고 suspend 옵션을 다르게 선택했을 때의 출력 결과다.
'항해99' 카테고리의 다른 글
[TIL] DAY12 (0) | 2023.04.15 |
---|---|
[TIL] DAY 11 (0) | 2023.04.13 |
[TIL] DAY 10 (0) | 2023.04.12 |
[TIL] Day8 (0) | 2023.04.10 |
[WIL] 항해 1주차 (0) | 2023.04.09 |