안녕하세요. 백준 chogahui05입니다. Vector는 ArrayList와 다르게, 주요 메서드에 synchronized가 붙은 클래스입니다. 여기서 질문 하나 드리겠습니다. 다음 코드는 어떻게 동작할까요?

 

 

 쓰레드 n개를 생성했다고 해 봅시다. 그러면 이 프로그램은, 항상 비어있는 벡터를 리턴할까요? 혹은, 항상 제대로 동작할까요?

 

 


 vector는 ArrayList와는 다르게 주요 메서드에 동기화 처리가 되어 있습니다. 이는 StringBuilder와 StringBuffer와의 관계와도 같습니다. 전자는 동기화 처리가 되어 있지 않고, 후자는 되어 있다. 정도로 이해하시면 됩니다. 실제로, Worker 클래스의 run 메서드 안에 있는 메서드 3개를 봅시다.

 

 

 isEmpty는 elementCount가 0인지를 검사합니다. synchronized가 붙어 있어요.

 

 

 다음에 size는 elementCount를 리턴합니다. 역시 동기화 키워드가 붙어있습니다.

 

 

 remove는, 이상해 보이는 메서드들을 많이 호출한 다음에, elementCount 값을 하나 감소시킨 다음에 oldValue를 리턴합니다. 역시 동기화 키워드가 붙어있어요. 그러니까, 제대로 동작하지 않느냐고 물어보실 수도 있는데요. 실제로, 대다수의 경우, Exception이 뜨지 않습니다. 정말 그럴까요?

 

 테스트 케이스를 임의로 여러개 만들어서 돌려볼 겁니다.

 

 

 이렇게 작성하면, 계속 돌 거에요. 5초가 되었던, 100초가 되었던 말입니다. 계속 돌려보겠습니다.

 

 

 그랬더니, IndexOutOfBoundException이 떠 버렸습니다. 특수한 경우에 대해서, 프로그램이 정확하게 수행되지 않았습니다. 이게 어떻게 된 일일까요? 이는 Thread의 stop 메서드를 다뤘을 때 언급한 키워드인 원자성과 관련이 있는 내용입니다.

 

 


 while Loop는, 다음과 같이 3개의 실행 과정이 있습니다.

 

 

 이 셋은 Vector에 대한 lock을 Thread가 획득한 경우에 실행할 수 있어요. 여기서 문제는, v에 원소가 하나 있었던 경우입니다.

 

 

 이 때, Thread1과 Thread2가 이런 식으로 수행이 될 수 있나요? 더 확대해서 상황을 보도록 하겠습니다. thread1과 thread2가, v가 비어있는지 메서드를 호출을 하려 한다고 해 봅시다. t1이, is_empty 메서드에 들어가게 되면, t2는 들어가지 못할 겁니다.

 

 

 다음에, v.isEmpty() 메서드를 t1이 끝냈다고 해 봅시다. 그러면, t1은 벡터 v의 동기화 영역에 들어가 있지는 않을 겁니다.

 

 

 상황을 그려보면, 이런 상황입니다.

 

 그러면, thread1은, 15번째 줄을 수행할 겁니다. thread1의 pc는 이 정보를 저장 할 거고요. 그런데, 이 때, v의 동기화 영역에 들어갈 수 있는 Thread가 둘인 것이 문제입니다. thread1, thread2. 만약에, thread2가 그 영역에 들어갔다고 한다면, t2도 isEmpty 메서드를 호출할 겁니다.

 

 

 v에 원소가 하나 있는 상황이였기 때문에, thread2도, true를 리턴할 겁니다. 그러면, t1도, t2도 15번째 줄을 수행하는 상황이 되어버립니다. v에 원소가 하나가 있음에도 불구하고. 그러면, remove가 2번 일어나게 됩니다. 이는, vector에서 맨 뒤에 있는 데이터를 제거할 때, 제가 empty한지 검사하고, size를 얻어오고, 그 위치를 제거하는 연산으로 쪼개버렸기 때문입니다. 메서드 3개가 atomic 하더라도, 3개를 붙여놓은 작업 T는 원자성을 만족하지 않는 셈입니다. 이 3개를 하나의 블록으로 묶어서 lock을 하지 않는 이상은 제대로 수행되지 않습니다.

 

 


 이제 상황을 하나 더 드리겠습니다. 다음 가상의 두 메서드 앞에 synchronized가 붙어있다고 생각해 봅시다.

 

 

 그리고, Worker의 오버라이딩 된 run 함수가 다음과 같다고 해 봅시다.

 

 

  이 경우에는 어떻게 동작할까요? 물론, remove_back은 없다지만, 있다고 가정해 보고요. 뭔가 달라진 것 같지만, 원자성이라는 관점에서 보면, 처음에 봤던 프로그램과 다를 게 없다는 것을 알 수 있습니다. vector가 비어 있는지를 판단하고, 맨 뒤에 있는 원소를 제거하는 연산이 나누어 질 수 있기 때문에, 아래와 같은 행동이 가능합니다.

 

 

 remove_back이 v의 size가 0일 때, 바로 리턴해 버리지 않는다면, OutOfIndexException을 띄울 수 있습니다. 이러한 문제에 대해서 설명하고 있는 문서가 있는데요. 코드가 3개 있습니다. 빨간색으로 칠해진 것은 잘못된 코드들입니다. 꼼꼼하게 읽어보시는 것을 권장합니다.

 

 그러면 이 글에 있는 코드를 어떻게 바꾸어야 할 지도 아실 수 있을 거에요.