All or nothing. 원자성이라고 이야기를 합니다. 어떠한 연산이 들어왔을 때, 실행이 되거나, 그렇지 않거나. 둘 중 하나의 상태가 되어야 합니다. 데이터 베이스에서 '트랜잭션' 이라는 것을 배우면 ACID라 해서 나오는 용어이기도 합니다.  아마, 그것을 배우시면 이런 그림도 많이 보셨을 거라 생각합니다.

 

 

 Commit은 Q가 반영이 된 상태를, Abort는 Q가 반영이 되지 않은 상태를 의미합니다. 만약에 Q를 execute 하라는 명령이 들어왔는데, 잘 수행하다가 실패한 경우에는 Fail로 가야 할 겁니다. 이 경우에는, 부분적으로 수행했던 명령들이 결과에 반영되면 안 됩니다. 잘 수행이 된 경우에는 둘 중 하나입니다. 취소를 하던지, 반영을 하던지.

 

 그러면, 위에서 말하는 '원자성'이 깨지는 경우에는 어떤 경우일까요?

 

 

 쿼리 Q를 처리하기 위해서, Q(1), Q(2), Q(3)이 필요하다 해 봅시다. Q(1), Q(2)만 실행이 성공적으로 일어났는데, Q(1)과 Q(2)만을 수행한 것이 결과값에 반영된 경우에 깨질 겁니다. 이 관점에서 보면, 잘 수행되고 있는 Thread를 갑자기 stop 메소드를 이용해서 강제로 종료시키는 것이 왜 위험한 일인지 아실 수 있을 듯 싶습니다.

 

 


 아래는 링크에 나온 코드를 변형한 코드입니다.

 

 

 먼저, Integer를 저장하는, Vector를 선언하겠습니다. ArrayList와 다른 점은, grow rate와 synchronized가 주요 메서드에 붙어 있다는 점입니다. Worker인 w는 이 Integer vector를 가지고 노는데요. 일단, 일꾼 w를 start 메서드를 호출해서 돌게끔 합시다. 그리고, Main Thread의 sleep time을 1초 정도 줍시다.

 

 

 

 run 메서드를 보겠습니다. 0번째 index에 계속 i를 넣고 있는데요. Vector이던, ArrayList이던지 이 작업은 생각보다 오래 걸릴 겁니다. 왜냐하면 맨 앞에 추가하기 때문입니다. 그리고, add 메서드가 수행이 잘 되다가 중간에 종료가 되어서 Inconsist 한 상태가 되었는지, 아니면 아예 수행이 되지 않거나, 수행이 완전하게 되어서 제대로 들어갔거나를 잘 볼 수 있을 거에요. 0번째 인덱스에서 추가 제거만 일어나기 때문이에요.

 

 이것이 어떻게 수행되는지 보겠습니다.

 

 

 index와 element를 넘기면 내부적으로 insertElementAt 메서드가 호출이 되는군요.

 

 

 그러면 insertElementAt을 보아야 겠습니다. 보니까, 확장이 필요하면 확장을 먼저 한 다음에, 배열을 복사하고, index에 해당 값을 넣고, size를 하나 증가시킵니다.

 

 


 예를 들어, [4, 3, 2, 1]이 있었다고 해 봅시다.

 

 

 이 상태에서 복사가 먼저 일어났다면, 아래와 같이 변할 겁니다.

 

 

 그 다음에 0번째 인덱스에 5라는 값을 넣고 size를 증가시킬 겁니다. 물론, 이 과정은 컴파일러가 어떻게 최적화를 잘 했느냐에 따라서 다를 수 있겠습니다만. 중요한 것은 add 연산이 완전히 일어났거나, 일어나지 않았다면 size가 n인 경우에 n-1, ... , 0 이 순으로 벡터에 저장이 되어 있을 겁니다.

 

 

 그런데, 실행 결과는 보면 그렇지 않습니다. 데이터의 특성을 보면, 286, 286, 285, ... 이런 식으로 되어 있어요.

 

 

 밑에 데이터는 161, 161, 160, ... 이런 식으로 되어 있고요. 그런데 size는 162라고 되어 있어요. 기존에 있었던 원소들 모두를 1칸씩 뒤로 미는 작업까지 수행하고 종료가 되었다는 것을 의미합니다. '불완전' 한 상태가 나타난 셈입니다. 이게 detect가 되지 않고 서비스가 된다면 상상만 해도 끔찍한 일일 겁니다. 링크에서 damaged Object라는 키워드가 나오는데요. 위 예제에서는 Vector u가 데미지를 입은 셈입니다.

 

 Thread의 stop 메서드를 쓰는 것이 위험한 이유 중 하나입니다.