우리네 장
[ OS ] 동기화 기법 소개3_Thread.join() 분석해보자! 본문
동기화 기법을 소개하며 보았던 Thread.join()..
Thread 클래스의 메소드들은 뭔가 명칭이랑 실 기능이 매칭되지 않는 것들이 있는 거 같다..
며칠 지났다고 그새 까먹은 동기화 ㅠ-ㅠ.. 다시 재적립 해보쟈!
일단, 매우 간단한 예시를 보며 설명하겠다.
예시로 들기도 민망한 코드..^^!
Thread 클래스를 상속받은, 1~100까지를 출력해주는 역할을 하는 스레드 3개를 만들었다.
일단 간략하게 Thread 메소드에 대해 짚고 넘어가자면,
- interrupt() : 실행 중간에 끼어드는 느낌인데, 놀랍게도 thread 실행을 멈추는 메소드.
- start() : 스레드를 실행시키는 메소드이다. run()과 다른 점은? start는 스레드 객체를 생성하여 실행한다는 것. run은 일회성으로 스레드 코드를 실행한다.
- join() : 오늘의 주인공인데, 간단히 말하면 다음으로 못 넘어가게 하는 메소드..?
위 코드를 실행하면 어떤 결과가 나올까??
Thread join method test start! j1 - current i :0 j1 - current i :1 j1 - current i :2 j1 - current i :3 j1 - current i :4 j1 - current i :5 j1 - current i :6 j1 - current i :7 ....... j1 - current i :96 j1 - current i :97 j1 - current i :98 j1 - current i :99 Thread join method test end! j2 - current i :0 j3 - current i :0 j2 - current i :1 j3 - current i :1 j2 - current i :2 j3 ...... |
현재 우리는 4개의 스레드를 돌리고 있는 것이다.
메인 스레드와 j1, j2, j3
만약 코드에서 j1.join()를 호출하지 않았다면
"~test end!"를 출력하는 부분은 윗 상단에 찍혔을 것이다.
즉 메인 스레드는 j1,j2,j3를 호출하고 본인의 나머지 코드를 실행 후 종료해버린다.
여기서, 메인 스레드 ( = initial Thread )가 다른 스레드들 보다 먼저 종료되는 것에 불편함을 느낀다면?
JVM이 java application을 동작하는데 메인 스레드가 먼저 종료되는 것은 아무 이상없다.
java application continues to execute as long as non-daemon threads are still running.
daemon 과 non-daemon이 헷갈리는 분들을 위하여...
- daemon thread : jvm instance가 돌아가는 thread를 의미, 즉 JVM 그 자체를 의미하는데 예를들면 GC가 되겠다
- non-daemon thread : java application의 initial thread와 이로인해 실행되는 다른 thread들을 의미한다.
즉, 메인 스레드는 여기에 속한다.
Q. 쨋든 j1.join()은 어떤 영향을 준 것일까??
A. 메인 스레드의 j1.join() 이후의 명령어를 실행하지 못하도록 하였다.
즉 join()는 이를 호출한 스레드를 wait 상태로 빠지게 하여, 이후 명령어를 실행하지 못하도록 한다.
이런 기능은 thread 간의 순서가 중요한 경우 순서 제어를 위해 사용되기도 한다.
위 코드 예시에선 j1 스레드가 반드시 j2, j3 스레드 보다 먼저 실행되고 종료된다.
분석을 위해 Thread 클래스의 join() 를 까보자.
join()는 파라미터를 넘길 수 있다. millisecond로 wait하는 시간을 의미하는데,
파라미터를 넘기지 않는 경우 join(0)과 같은 의미이다.
아래 두 스레드의 흐름을 표현해 보았다.
System.out.println | |
j1.start 시작 | j1.run 실행 |
j1.join 호출 | j1.run 실행 |
j1.run 실행 | |
j1.run 실행 | |
.... | |
j1.interrupt 스레드 종료 | |
j2.start 시작 | |
j3.start 시작 | |
System.out.println |
1. 메인 스레드는 j1 스레드의 join 메소드를 호출한다.
join 메소드는 "synchronized" 가 붙어 mutex = monitor lock 을 사용하게 된다.
2. 메인 스레드는 j1 객체의 monitor lock을 쥔 채 메소드 코드를 실행한다.
3. 파라미터 없이 호출했기 때문에, if( millis == 0 )에 걸려 if( isAlive() )를 실행하게 되는데,
이때 isAlive의 대상은 j1 객체가 된다.
4. j1 객체는 아직 실행 중이기 때문에 메인 스레드는 wait(0) == wait()을 호출하게 되어,
j1의 condition variable의 wait set에 들어가 잠들게 된다.
5. 여기서 메인 스레드가 wait 상태에서 벗어날 수 있는 방법은,
1 ) 파라미터로 받은 millis 가 지나거나 ( 이때는 애초에 TIME_WAITING 상태 )
2 ) 다른 스레드가 j1 객체에 대해 notify() 혹은 notifyAll()을 해주거나
3 ) j1 스레드가 interrupt, 즉 종료되면 된다.
6. 이 상황에서는 3 ) 방법 외에는 가능한 것이 없으므로, j1이 종료되고 나면 메인 스레드는 WAITING 상태에서 벗어나
READY 상태로 변하여 scheduling 대상이 된다. 즉, ready queue에서 대기
7. scheduling 대상이 되어, 이후 명령어 실행.
갑자기 헷갈리는데..
JAVA는 기본적으로 Monitor ( = mutex lock ) 을 사용하고, signal and continue 방식이다.
lock 과 unlock은 lock을 쥔 동일한 객체만 가능하지만, - lock.acquire 및 lock.release
notify 및 notifyAll은 wait 상태가 아닌 다른 스레드가 해줘야 한다.
둘이 완전 별개!!
모니터 및 뮤텍스 락을 적용해 볼 수 있는 아주 좋은 예시였다!
'OS' 카테고리의 다른 글
[ 동기화 #3 ] 세마포어 (1) | 2023.12.27 |
---|---|
[ 동기화 #2 ] 뮤텍스 (3) | 2023.12.27 |
[ 동기화 #1 ] 스핀락 (0) | 2023.12.27 |
[ OS ] 동기화 기법 소개2_모니터! ( monitor! ) (1) | 2022.09.16 |
SSH, Telnet 프로토콜 비교 (0) | 2021.01.21 |