오랫만에 스레드 포스팅을 올립니다. Java에는, synchronized 키워드가 있는데요. 이것은 특정한 대상에 lock을 걸어서, lock이 풀릴 때 까지 다른 쓰레드가 실행을 하지 못하게 합니다. static 메소드에 붙일 때와, 일반 메소드에 붙일 때, 어디에 락을 거는지가 다른데요. 그에 대해서 간단하게 짚고 넘어가 보도록 하겠습니다.

 

 


 먼저, 일반 메소드에 synchronized를 붙였습니다. 그러면 어떻게 락을 걸까요? 간단하게 프로그램을 만들어서 실험해 봅시다. 예제 1번입니다. 먼저 코드부터 보여드리겠습니다.

 

 먼저 foo와 bar는, static 메서드가 아니지만, synchronized가 붙어있습니다. 이들을 동기화 메소드라고 이야기 합시다. 그리고 Thread를 상속받은 worker와 worker2 클래스가 있습니다. 이들은 아래와 같습니다.

 

 

 먼저 worker는 shared된 객체에서, foo라는 메소드를 호출합니다.

 

 다음에 worker2는 shared된 객체에서 bar라는 메소드를 호출합니다. Main에서 어떻게 부를 거냐면요. 당연하게도 worker 객체 하나랑 worker2 객체를 생성을 한 다음에 start 메소드를 호출할 겁니다.

 

 

 그러면 실행 결과가 어떻게 나올까요? shared의 동기화 메소드가 Thread 하나가 실행하고 있을 때, 다른 쓰레드는 shared의 동기화 메소드를 execute 할까요? 결과는 아래와 같이 나옵니다.

 

 

 여기서 중요한 점은, 객체 o의 동기화 메소드를 쓰레드 하나가 호출을 해서 lock을 한 경우, 객체 o의 다른 동기화 메서드가 다른 쓰레드에 의해 호출이 되지 않습니다. lock이 풀릴 때 까지 기다립니다. 이는 1이 다 출력이 되고 나서 2가 출력된 것을 봐도 알 수 있습니다.

 

 즉, Thread A가 o의 동기화 메소드를 실행하고 있을 때, Thread B가 o의 다른 동기화 메소드를 실행하지 않는다는 겁니다.

 

 

 그러면 이 경우에는 어떨까요? worker2가, work를 호출하고 있습니다. 이것은 동기화 메소드가 아닙니다. 그러면 어떻게 동작하는지 봅시다. 실행 결과를 보면 아래와 같습니다.

 

 

 보시면 1이 출력되는 도중에 2가 출력됨을 알 수 있어요. 이는, shard 객체에 대해서, 동기화된 블록이나 메서드에 대해서만 lock이 일어났다는 거에요. work는 일반 메소드이기 때문에, lock이 되어 있어도, 수행이 되었다는 이야기가 되겠습니다.

 

 


그러면 다른 객체에 대해서는 어떻게 동작할까요?

 

 일단 worker2의 run 메소드는, shared가 가리키는 객체의 특정 메소드를 실행하게끔 되어 있습니다. 이것은 동기화 메소드입니다. 그리고 main은 아래와 같습니다.

 

 보시면 worker a에 넘어가는 객체 shared랑, worker2에 넘어가는 shared2는 다릅니다. 메모리 상의 다른 공간을 가리키고 있습니다. 만약에 객체 단위로 락이 걸린다면, a에 넘어간 A 객체랑, b에 넘어간 A 객체가 서로 다르기 때문에, 별개의 lock이 걸려버립니다.

 

 

 예를 들어, worker 쓰레드가 먼저 실행되어서, shared 객체에 먼저 걸렸다고 해 봅시다. 그런데, worker2는 shared가 아닌, shared2를 씁니다. 여기에는 아직 lock이 걸리지 않았습니다.

 

 

 그러면, shared2에 대해서 lock이 되지 않았기 때문에, a가 다 끝나고 수행되는 것이 아니라, a가 수행되는 도중에 b도 수행이 될 수 있을 거에요. 실행 결과는 아래와 같습니다.

 

 

 보시면 1 사이에 2가 끼어 있는 게 보이실 거에요. static이 아닌, 일반 메소드에 synchronized를 걸어버리면 객체 단위로 잠긴다는 것을 기억하시면 좋겠습니다. 그리고 Thread A가 객체 o의 동기화된 메서드를 실행할 때, Thread B가 o의 sync가 된 메서드를 수행하지 못해요. lock이 풀릴 때 까지. 하지만, Thread B가 o의 일반 메소드는 수행할 수 있어요. sync가 걸려있는 블록은 수행하지 못하지만요. 그 점 짚고 넘어가시면 좋겠습니다.

 

 

 

 그러면, static 메소드 앞에 붙이면, 어느 단위로 락이 걸릴까요?

 

 

 일반 메소드에 sync를 걸고, 동기화 메서드를 실행했습니다. 그리고 Thread dump를 띄웠습니다. 여기서 중요한 것은, locked 뒤에 붙어있는 것인데요. uni.A에 대해서 걸려있다는 것을 나타냅니다.

 

 

 그런데 이건 이야기가 조금 달라요. lock이 되었는데, java.lang.Class에 대해서 걸려있어요. 이것은 어떤 차이 때문에 그렇게 된 것일까요? 하나는 static 앞에 붙였을 때, 하나는 일반 메서드에 sync를 붙였을 때입니다. lock을 거는 대상이 다르다는 것은 굉장히 중요합니다. 이것도 간단하게 실험을 통해 알아보도록 합시다.

 

 

 먼저, st_foo와 st_foo_2를 선언하였습니다. 이것은 클래스 A의 static method입니다. 그리고 worker1과 worker2의 run 메소드는 아래와 같습니다.

 

 

 대충 이해가 가시나요? worker1의 shared와 worker2의 shared 필드는 다른 객체입니다. 이렇게만 바꾸고 프로그램을 실행시켜 봅시다. 어떻게 나오나요?

 

 

 1 다음에 2가 오거나, 2 다음에 1이 옵니다. 서로 다른 객체인데도? 그 이야기는 클래스 단위로 락이 걸렸다는 이야기가 됩니다. 그러면 이 경우는 어떨까요?

 

 

 worker는 st_foo를, worker2는 st_foo_2를 호출하였습니다. 이 때에는 또 실행 결과가 어떻게 나올까요?

 

 

 어? 이건 뭘 의미할까요? 클래스 AA의 static 동기화 메소드를 Thread 1이 실행하면, Thread 2가 클래스 AA의 static 동기화 메소드를 실행하지 못한다는 이야기입니다. lock이 풀릴 때 까지요.

 

 

 그러면 이 경우는 어떨까요? 보시면, worker 객체는 foo를, worker2는 st_foo_2를 실행합니다.

 

 

 실행 결과는 이렇게 나옵니다. 이건 또 왜 그럴까요? 천천히 봅시다. worker가 run 메소드를 실행해서, shared 객체의 foo 함수에 접근하는 순간, shared 객체에 LOCK이 걸립니다. 그 경우에는, shared의 동기화된 메서드나, 동기화 블록에만 접근하지 못해요. 그런데, 클래스 단위로 lock이 걸리지 않았어요.

 

 

 그러니 static sync 함수인 st_foo_2가 수행이 됩니다. foo를 다른 쓰레드가 실행하던, 말던. 이는 LOCK의 대상이 서로 다르기 때문에 일어난 현상입니다. 잠김이 걸리는 대상과 단위에 대해 아는 것은 상당히 중요합니다. 다음에 몇 가지 동기화 문제를 예로 들면서 이어가도록 하겠습니다.