멀티 스레드 프로그램에서 여러 쓰레드(Thread)가 동시에 하나의 자원에 접근할 수 있다. 여러 쓰레드가 동시에 공유 자원에 대한 수정을 할 경우 동시성 문제가 발생할 수 있다. 자바(Java)에서는 다행히 synchronized 라는 키워드를 제공하여 별도의 라이브러리를 사용하지 않고도 손쉽게 멀티 쓰레드 환경에서의 동시성 제어를 구현할 수 있다.
synchronized 키워드를 사용할 때 변수를 명시하여 해당 변수에 접근하는 쓰레드를 줄 세울 수 있다. 예를 들어 다음과 같은 코드를 보면,
synchronized 키워드와 함께 레퍼런스(Reference;참조) 변수를 넘겨줬다. 두 개의 쓰레드가 같은 코드를 수행할 때, 이 레퍼런스 변수를 기준으로 줄서게 된다.
이 코드를 실행하면 다음과 같은 결과를 얻을 수 있다.
critical section start [13]
Work within critical section[13]
critical section end [13]
critical section start [14]
Work within critical section[14]
critical section end [14]
13번 쓰레드와 14번 쓰레드가 동시에 수행된다. 그러다가 synchronized 키워드에서 줄서게 되어 13번 쓰레드가 먼저 Critical Section으로 들어간다. 이후 10초간 Sleep 한 다음 synchronized 블럭을 빠져나가고, 기다렸던 14번 쓰레드가 이후 수행을 이어간다. 우리가 의도했던 synchronized 블럭의 동작대로 수행된다.
다음 코드를 살펴보자.
두 쓰레드가 같은 레퍼런스 변수를 synchronized 블럭 시작점에 사용했다. 첫 번째와 두 번째 쓰레드 사이에 Sleep 을 집어 넣었고, Critical Section 안쪽에서 synchronized 블럭에 명시한 레퍼런스 변수에 새로운 할당을 한다.
이 코드를 실행하면 다음과 같은 결과를 얻게 된다.
critical section start [13]
Work within critical section [13]
critical section start [14]
Work within critical section [14]
critical section end [13]
critical section end [14]
두 쓰레드가 같은 Critical Section 을 수행하게 된다. 이런 동작을 의도 했을 수도 있겠지만 많은 경우에는 의도하지 않은 동작이다. 이 코드에서는 일부러 두 쓰레드 사이에 Sleep 을 넣었지만 스케줄링에 의해서 얼마든지 두 쓰레드 실행 사이가 벌어질 수 있다. 타이밍 이슈가 발생할 수도 있다는 의미다.
이 문제의 원인은 synchronized 블럭에 명시했던 레퍼런스 변수를 블럭 안쪽에서 수정했기 때문에 발생한다. 첫 번째 쓰레드가 원래 있던 레퍼런스를 기준으로 Critical Section을 만들었지만 Critical Section 안쪽에서 멤버 변수를 바꿔버리면 뒤이어 들어오는 쓰레드는 원래 있던 레퍼런스가 아닌 이전에 수행된 쓰레드가 바꿔놓은 새로운 객체를 기준으로 Critical Section을 만든다.
두 쓰레드는 같은 코드를 수행하지만 synchronized 대상 객체는 분명 다르기 때문에 동시에 수행될 수 있는 것이다.
이 경우 IDE 에서 똑똑하게 경고(Warning)를 뿌려줘서 문제가 있을 수도 있음을 경고해준다.
"Synchronization on a non-final field 'this.object'" 라는 경고 문구가 뜬다. synchronized 블럭에 final 변수가 아닌 레퍼런스 변수를 명시하면 이 레퍼런스가 변하지 않음을 보장할 수 없기 때문에 이와 같은 문제가 발생할 수 있다.
이런 류의 버그는 잘 발생하지 않지만 한번 발생했을 때 동시성 문제를 발생시켜 코드 수행을 엉망진창으로 만들 수도 있으므로 주의해야한다.
댓글