이번 시간에는 Java의 yield 메서드에 대해서 알아보도록 하겠습니다. 사실, 이것은 백준 사이트에서 yield 관련한 질문이 들어와서 레퍼런스 보면서 조금이나마 알게 되었습니다. 물론, 그 질문에 대한 답은 pc 레지스터와 쓰레드가 어떤 영역을 독립적으로 가지는지에 대해서 찾아보세요. 였지만요. 일단 이 함수는, 설명부터 보는 게 중요할 듯 싶어요. 레퍼런스 사이트에 있는 설명 중 일부만 보도록 하겠습니다.

 

 current thread와 스케쥴러가 나오고, 힌트가 나옵니다. 정확하게 해석은 못 하겠지만, 현재 돌고 있는 쓰레드에 대한 무언가의 힌트인 것으로 보여요. 계속 보면 is willing to yield가 나오는데, 양보를 한다는 것이에요. 무엇을? 그것의 current use를. 쓰레드가 프로세서에 올라와 있을 건데, 그것을 기꺼이 양보한다는 Hint. 뭐 이런 식으로 해석하면 무난할 듯 싶어요. 혹시나 정확하게 해석하실 수 있는 분이 계시다면 댓글로 알려주시면 감사하겠습니다.

 

 그 다음 문장이 중요한데요. 이 스케쥴러는, 이 힌트를 무시할 수도 있다는 것이 있습니다. 이 힌트가 도대체 무엇일까요? 현재 돌고 있느 쓰레드가 다른 Thread를 위해서 양보하겠다는 것입니다. 그러니, yield를 호출한다고 해서, 돌고 있는 쓰레드가 항상 양보한다. 그렇다는 보장은 없어요. 단순히 시험 문제를 풀 수 있는 힌트만 알고 있는 것과, 그 문제를 푸는 것은 의미가 다릅니다. 마찬가지입니다. 이 부분은 잘 보실 필요가 있어요.

 

 


 쓰레드의 상태를 가지고 오겠습니다.

 

 이 중에 yield는 run 중인 쓰레드를 runnable한 상태로 만들 수 있는 메소드입니다. 물론 힌트를 통해서요. 운이 좋게도, 스케쥴러가 쓰레드 A가 yield 한다는 정보를 받아서, 상태 state를 바꿀 수도 있을 겁니다.

 

 

 그러면 Thread 1이 빠졌다고 해도 실행 대기 상태에 들어갑니다. 그렇다는 이야기는 Thread 1이, 언제든지 다시 선택이 될 수 있는 이야기도 됩니다. 이건 구현이 되어 있는 스케쥴러의 마음일 거고요. 한 마디로 요약하자면, 실행 결과를 속단하기 어렵다는 뜻이 되겠습니다.

 

 


 아래와 같은 프로그램을 생각해 봅시다.

 

 

  ShareBoard라는 클래스가 있어요. 이 안에, sum 이라는 공유 변수가 있어요. Board 객체를 생성하면 sum이라는 필드는 Heap에 생성이 될 거에요. 대략적으로 그려보면 아래와 같습니다.

 

 

 그러면 Thread1과 Thread2가 공통적으로 heap에 할당된 SharedBoard의 sum에 접근을 할 겁니다. 이 때, sum = sum + dx는 원자적인 연산이 아닙니다. 메모리에 값을 끌어오고, 레지스터에 값을 더해서, 메모리에 새로운 값을 쓰는 몇 단계로 이루어져 있는데요. 이를 수행하던 도중에, 다른 쓰레드가 끼어들면 잘못된 값이 업데이트가 될 수 있을 거에요.

 

 

 실제로 한 쓰레드는, 1만번 10을 더해요. 다른 쓰레드는 1만번 10을 뺍니다. 그러면, board의 sum 값은 10만 - 10만 = 0이 되어야 정상일 거에요. 그런데, 실행할 때 마다 다른 값이 나오는데요.

 

 이는, 두 쓰레드가 sum에 무언가를 write를 같이 할 때, 값의 갱신이 이상하게 되어버리기 때문입니다.

 

 

 이렇게 바꿔버리면 이야기가 달라지는데요. t1과 t2가 접근하고 있는 상황에서, t1이 add를 호출했다고 합시다. 즉, ShardBoard의 this 객체에 대해서, LOCK을 획득했다고 해 봅시다.

 

 

 이 때 thread2가 sb.add. 그러니까 sb에 접근하려고 한다면, BLOCK이 됩니다. thread1이 사용 중이기 때문입니다. 이 경우, t2는, yield가 되는 대상에 고려가 되지 않습니다.

 

 

 runnable인 상태가 아니기 때문입니다. add 메서드를 t1이 끝냈을 때, 객체에 LOCK이 걸린 상태가 아닙니다. 그렇다면, t2도 sb의 add 메서드를 실행할 수 있는 기회를 얻을 수 있을 거에요.

 

 

 이 때, Hint가 날라간 상태에서는, 스케쥴러가 다른 쓰레드를 올리려고 할 때 'T1이 yield를 날렸으니까 다른 쓰레드에게 양보를 하겠거니' 라는 정보를 참고는 할 수 있을 거에요.

 

 


 그러면 2개 더 질문을 드리겠습니다. 아래 코드를 고려해 봅시다.

 

 

 A1이라는 이름을 가진 Thread와 B1이라는 이름을 가지는 쓰레드가 각각 SharedBoard 객체 sb의 add 메서드를 100번 호출하였습니다. 결과가 항상 같게 나올까요? 사실 항상이라는 것과 같이 극단적으로 수식하는 말은 조심할 필요가 있습니다. 커뮤니티 할 때도 새겨 들으면 좋은 팁 중 하나입니다. 심지어, Thread의 yield를 호출했을 때, 스케쥴러에게 Hint만 제공한다고 되어 있었어요. 문제를 푸는 데 힌트만 얻었다는 문장과, 그것을 통해서 문제를 풀어버렸다는 문장의 의미 차이를 헷갈리시는 분은 없을 겁니다. 문제를 푸는 데 힌트만 얻었다면 풀 수도 있고 못 풀 수도 있을 겁니다. 일례로 영어 빈칸 뚫기 3점짜리 문제에 대해서, 이 글이 어떤 카데고리의 글인지에 대해서 힌트를 얻었다고 항상 그 문제를 맞출 수 있는 것은 아닌 것 처럼요. 즉, yield를 호출했다고 해서, 현재 실행되고 있는 쓰레드가 그 즉시, 다른 쓰레드에게 실행을 양보하고 실행이 가능한 RUNNABLE한 상태가 된다는 보장은 없어요.

 

 그렇게 생각하신다면, 실행 결과가 왜 다른지도, 이해를 하실 수 있을 거라 생각이 됩니다.

 

 1번째 실행 결과는 A, B, B,  B, A, B, A 순으로 수행되었습니다.

 

 

 그런데 2번째 결과는 A, A, A, A, B, B, A 이 순으로 수행이 되었습니다. 즉, yield 메서드가 다른 쓰레드에게 양보를 해 준다고 해석하면 조금 힘들어요. 'T1이 Runnable한 상태인 특정 조건의 Thread에게 양보를 한다는 힌트'를 줄 뿐입니다. 이제, 다른 질문을 해 보겠습니다. 백준 사이트에서 실제로 답변을 해 준 질문은, 이 메서드가 호출이 되면, 다른 쓰레드는 계속 양보만 해 줄 거 같은데, 왜 정상적으로 실행이 되느냐는 것이였습니다. 이에 대한 것은 다음 시간에 알아보도록 하겠습니다.