Collection 중에는, SynchronizedMap이 있습니다. 대충 Synchronizedxxx라고 해 보겠습니다. 이것들은 어떠한 구조로 되어 있을까요? 먼저 Collcetions.Synchronizedxxx로 호출하는 것을 보아서는, 내부 class로 선언이 되어 있는 것을 알 수 있습니다.

 

 

 이들은, Map<K,V> 꼴의 맵 객체와, Object 객체인 mutex를 가지고 있습니다. 생성자 안에는, 인자로 넘겨받은 Map을 대입하는 부분이 있습니다. HashMap도 있고, TreeMap도 있을 텐데. 이렇게 해도 문제가 없나요? 고양이는 동물이고, 개도 동물이잖아요. 그러니 이들을 동물이라고 하는 것은 크게 무리는 없습니다.

 

 다만, 넘겨받은 객체를 가지고 bark를 호출할 때에는 이야기가 달라지는데요. 개는 '멍멍', 고양이는 '야옹' 이라고 할 거에요. 이를 다형성이라고 해요.

 

 

 주요 함수들을 보면, synchronized(mutex)가 걸려있음을 알 수 있는데요. 이미 mutex에 누군가 접근을 하고 있는데, 다른 스레드가 접근하려고 하면 block이 됩니다.

 

 

 이는 put이나 remove와 같은 객체에 쓰기를 수행하는 것들도 예외가 없습니다. 생성자에 그냥 Map을 implements 하는 것을 넘긴 경우에는, SynchronizedMap 자체에만 걸립니다. 더 쉽게 말하면, Map을 Boxing한 것이 SyncMap입니다.

 

 

 그리고, 이 안에 mutex가 있습니다. 이것이 Sync된 Map, 혹은 자료구조에서, put이나 remove를 호출하였을 때, 잠금의 대상이 되는 객체입니다. 물론, mutex가 this였다면, Sync된 Map 그 자체일 겁니다. put 메소드에서 m.put 하는 부분으로 들어가 보겠습니다.

 

 

 그러면 HashMap의 put 메소드로 갔음을 알 수 있습니다. 만약에 생성자에 TreeMap 객체를 넣었다면, TreeMap의 put이 호출이 될 겁니다.

 

 

 Debug 창을 보면, 호출 trace가 나옵니다. 여기서 owns라는 것을 보면, id가 19인 Collections.Syncxxx에 걸었음을 알 수 있습니다. 이것을 T1이 소유하고 있다는 것을 의미합니다.

 


 이제, 아래의 코드를 생각해 보겠습니다. Thread를 extends한 Worker는 다음과 같습니다.

 

 

 HashMap과 Map이 있습니다. 생성자의 1번째 인자가 무엇인지 봐야 겠네요.

 

 

 그리고 run 함수입니다. f가 0일 때에는 hm에 i라는 key를 넣고, f가 1일 때에는 h에 160+i라는 key를 넣습니다.

 

 

 Main 함수를 보면, HashMap hm을 선언했습니다. 그리고, h는 Syncxxx 안에 hm을 포장한 객체입니다. 다음에, Worker를 생성할 때, hm, h, flag를 순서대로 넣는다는 것을 알 수 있습니다.

 

 

 이를 그림으로 나타내면 위와 같습니다. worker 둘을 동시에 돌릴 겁니다. key 값은 모두 다르기 때문에, 제대로 들어갔다면, h의 크기는 20이 나와야 할 겁니다. 항상 w1과 w2가 종료된 후에는, h 안에 키가 20개가 들어있을까요?

 

 


 결론부터 말하면 아닙니다.

 

 19, 18, 19. 생각보다 이런 일이 자주 발생했음을 알 수 있습니다.

 

 

 이는 w2가 h.put을 호출했을 때, 잠금이 일어나는 대상이, SyncXXX이기 때문입니다. worker1이 hm.put을 호출한다고 하면, 타겟인 hashmap, hm은 잠겨있지 않습니다. 누군가 lock을 가지고 있지 않습니다. 따라서, w2가 h.put을 수행하는 도중에, 중간에 w1이 끼어들어서 hm.put을 할 수 있습니다.

 

 그리고 h는 hm을 포장한 syncMap이였습니다. 당연하게도, hashmap 객체인 hm에, 두 스레드가 동시에 put 하는 상황이 발생하게 됩니다. 단지, SynchronizedMap과 같은, 포장된 객체인 SyncXXX 클래스는, 잠금이 된 포장만 제공할 뿐입니다.

 

 

 즉, SyncXXX에 동시에 접근하려고 할 때 막아주는 것 뿐입니다.

 

 

 제가 예시로 든 프로그램과 같이, MapXX에 직접 T2가 접근하고 있고, T1은 SyncXXX에 접근하면 사실 소용이 없습니다. lock을 획득하지 않아도 T2는 들어올 수 있기 때문입니다. lock이 걸리는 대상을 이해하는 것은 중요합니다. 단순히 MapXX에 걸렸다면, 이야기는 달라졌을 겁니다.