race condition은 경쟁 상태를 의미합니다. 둘 이상의 무언가가 자원에 접근한다고 생각하면 어떨까요? 그리고 그것의 상태를 바꾸려고 한다면 어떨까요?

 


 아래 프로그램을 보겠습니다.

 

 리소스인 LinkedBlockingQueue가 있습니다. BlockingQueue는 쉽게 말해 동기화가 된 큐라고 생각하시면 됩니다. Executor 내부 구현에 쓰이기도 합니다. 여기에서 BlockingQueue에 대해 심도 있는 내용을 다루지 않겠습니다. 여기에서는 내부적으로 lock을 이용해서 Thread Safe 하다는 것 정도만 이해하셔도 무난합니다. 

 

 

 Worker1은 Queue에 수를 추가합니다. 0부터 99까지 순서대로 추가를 합니다.

 

 

 그리고, Worker2는 수를 뺍니다.

 

 

 Main 함수에서는 worker1과 worker2에게 일을 시키고, w1과 w2가 일을 끝날 때 까지 기다립니다. 실행 결과는 어떻게 나올까요? 너무 기니까, 일부분만 캡쳐해 보겠습니다.

 

 

 먼저, 9, 10, 11이 추가되고 9가 삭제되고, 12가 추가되었다는 내용이 출력됩니다.

 

 

 그런데, 다음에는 9갸 추가되고, 8이 삭제되었다는 내용이 출력됩니다. 여기서 한 가지 의문. 연산을 하고 나서 add나 delete가 되었다는 것이 출력이 되는데, 연산을 다 하고 나서, 다른 스레드가 끼어들 수 있지 않을까요?

 

 네. 맞습니다. add()라던지 poll()을 호출할 때에는 모르겠지만, Worker1의 run, Worker2의 run에 실행 흐름이 넘어오면, 둘 다 어떠한 잠금을 가지고 있지 않습니다. 그러니, 언제 끼어들어도 이상한 상황이 됩니다. 예를 들어서, Worker1이 add() 메서드의 수행을 끝냈을 때, Worker2가 poll() 연산을 수행하고 delete 되었다는 메세지를 출력할 수도 있습니다. 아이템을 넣고 출력하는 작업과, 아이템을 빼고 출력하는 작업을 하나로 묶어보도록 하겠습니다.

 

 


 Worker1의 run 메소드를 보겠습니다. shared.bq에 synchronized를 걸었음을 알 수 있습니다. 이 블록 안에서 add 연산을 수행하고 큐에 추가가 되었다는 메세지를 출력하는 작업이 있습니다.

 

 

 다시 Worker2의 run 메소드를 보겠습니다. 마찬가지로 앞에 원소를 빼고, delete 되었다는 메세지를 출력하는 작업을 sync 블록 안에 넣었다는 것을 알 수 있어요. 두 경우 다 shared.bq에 거는 것이므로, 어느 한 Thread가 synchronized(shared.bq) 안에 들어가 있다면, 다른 하나가 shared.bq를 건드리려는 순간에 block이 되어 버립니다.

 

 결국, 저 블록은 atomic 하다고 봐도 무난합니다. 그러면 실행 결과는 어떻게 나올까요? 일부 결과만 보겠습니다.

 

 

 25가 추가 된 다음에 1이 제거되었다고 출력되었습니다.

 

 

 그런데, 또 실행을 시켜 보니, 25가 추가 된 다음에 26이 추가되었다고 나왔습니다. 왜 그런 걸까요? 추가하고 그 결과를 출력하는 거나, 제거하고 그 결과를 출력하는 것 자체는 원자적입니다. 그런데, 3개의 원소를 추가하고, 그 결과들을 출력하는 것은 원자적인 연산이 아닙니다. 3개의 원소를 제거하고 그 결과들을 print 하는 거 역시 원자적이지 않습니다. 이는 링크에서도 설명을 했습니다.

 

 이것을 이해하기 쉽게 그림으로 그려보겠습니다.

 

 

 w1, 생산자가 BlockingQueue, 공유자원에 1과 2를 추가했습니다. 그 다음에는 3을 추가해야 할 겁니다. 그런데, p가 2를 추가를 끝낸 상황에서는 p와 c가 서로 공유 자원에 대한 쓰기 권한을 가져가려고 할 겁니다. 예를 들어서, 그 다음에 쓰기 권한을 p가 가져갔다고 해 보겠습니다. 이 경우에는 p가 이긴 경우입니다.

 

 

 그러면, 큐에는 3이 들어갈 겁니다. 반대로, 1과 2가 추가된 상황에서 w2, 그러니까 c가 write 권한을 가져갔다고 하면 어떨까요? w2는 큐에서 빼는 작업을 합니다.

 

 

 그러면, 1이 빠질 겁니다. 문제는, w1과 w2가 blockingQueue에 서로 접근하려고 했기 때문에 발생하였습니다. 서로 다른 흐름이, 같은 자원에 접근하려고 할 때, race condition가 발생합니다. 이 흐름은 프로세스가 될 수도 있고, 스레드가 될 수도 있을 겁니다. 같은 자원은 힙에 생성된 객체 말고도, (프로그램에 따라서는) 파일이나 스트림 등이 될 수도 있을 겁니다.