Spring 의 Bean 들은 대게 Singleton 패턴으로 정의되며 Application Context 에서 관리되는 형태로 개발자는 사용하게 된다.


그렇게 때문에 상당수의 개발자들이 Spring 의 각 Bean 들은 Thread Safety 가 보장된 안전한 Bean Object 이며 Spring 은 해당 동시성 문제에서 자유롭다고 여기는 경향이 있다.


하지만 정확히 Spring Bean 들은 Thread 의 Safety 와 무관하다. 


Spring Container 는 Object 들 각각에 대한 Life cycle 을 갖고 있으며 Container 내에서 하나만 존재하도록 보장하지만 그것이 Thread-Safe 를 말하는 것은 결코 아니며, Spring framework 는 오히려 이 책임을 개발자에게 맡긴다.


만약 Non-Thread-Safe Object 가 Injection 된다면 해당 객체는 쓰레드에 안전하지 않으며 개발자가 직접 핸들링해주어야 한다.


그렇다면 Bean 에 대해 어떤 방식으로 Thread Safety 를 부여할 수 있을까?


(1) Builder Pattern 의 사용

  간단하면서도 Tricky 한 방식으로 Builder Pattern 을 사용하면 좋다.

 Builder 패턴을 이용하면 객체의 Setter 를 정의하지 않은 상태에서 생성자만으로 객체의 Mutation 을 관리할 수 있으며, Spring 의 Bean 들은 Container 에 의해 life cycle 관리가 위임된다.

 따라서 Builder Pattern 을 통해 Set 을 관리하면 간단하면서도 명확히 Thread Safe 를 구현할 수 있다.


(2) Stateless Bean

  Bean 을 상태값과 무관하게 동작할 수 있는 Bean 으로 설계하는 것이다. 

  Bean 이 특정 상태를 나타내는 변수를 계속 메모리에 들고 상주하는 형태가 아닌, 가장 단순한 형태의 도메인 모델로 사용이 추천된다.


(3) Lock the beans

  가장 최후의 수단으로 여겨야 하는 방법으로 Bean 에 대해 Thread Safe 하게 설계를 하는 것이다. Spring 은 Lower level Library 들을 통해 Bean 단위의 Lock 을 지원하고 있으며 이를 통해 병렬처리에 있어서 동시성 문제의 해결이 필요하다.


물론 실제로 Safe 하지 않은 상황을 고려해야할 경우는 많지 않지만, 각기 다른 Request 를 공통으로 분류해야한다거나, 내부에서 Internal Thread 를 구동하는 경우에는 반드시 신경써보자.





단일 프로세스 & 쓰레드를 갖는 프로그램을 개발할 경우에는 신경쓸 일이 거의 없지만 여러 작업루틴이 동시에 수행되며, 공유 리소스에 접근하게 되는 동시성 프로그래밍을 할 경우에는 반드시 신경써주어야 할 부분이 바로


Mutual Exclusion (상호배제) 문제이다. 이는 공유 불가능한 자원을 동시에 사용하게 될 경우 발생할 수 있는 충돌을 방지하기 위해서 Critical Section 을 만들고, 해당 영역에서 데이터를 사용하게끔 하는 방법을 사용한다.


(1) 세마포어(Semaphore)


이렇게 공유자원에 대한 동시성 문제가 발생하였을 때, 즉 여러 개의 프로세스 또는 쓰레드가 동시접근하는 문제를 방지하기 위해 고안된 것이 바로 세마포어이다. 


세마포어란 리소스의 상태를 나타내는 카운터를 지정하여 다중 프로세스에서 행동을 조정 및 동기화 시킬 수 있는 기술이다. 

여러 프로세스가 접근 시 여러 개의 Lock 을 할당하여 동시에 허용 가능한 Counter 의 제한을 둔다. 카운터가 1개로 0 / 1의 값을 가질 때 Binary Semaphore 라하고 이는 Mutex와 동작이 같다.


(2) 뮤텍스(Mutex)


뮤텍스는 Lock 을 가지고 있을 때에만 공유자원에 접근이 가능하게끔 하는 로직이다. 

즉, 세마포어가 여러 개의 락을 두어 제한된 리소스 접근을 허용하는데 반해 뮤텍스는 오로지 한 개의 쓰레드/프로세스만 할당한다.



* 세마포어(Semaphore) 와 뮤텍스(Mutex) 의 차이점


- 세마포어는 주로 시스템적 범위에 적용이 되며 뮤텍스는 프로세스 내에서 적용이 된다. 

(물론 세마포어와 뮤텍스는 매커니즘의 개념이기 때문에 국한된다고 할 수는 없다.) 주로 뮤텍스는 프로세스 내 쓰레드간 자원 접근에 대하여 적용이 되며 Lock 한 쓰레드가 Unlock 도 해주어야 한다. 

반면 세마포어는 Lock 을 건 소유주가 아니더라도 Unlock 이 가능하다.


- 세마포어는 동기화 대상이 하나 이상일 때, 뮤텍스는 하나일 때 사용된다.


동기화 처리 로직은 Lock 을 수반하며 이 Lock 이 여러 개의 작업 큐 내에서 걸릴 때 DeadLock 이 생길 우려가 있다.


데드락은 임계 영역 내의 전역 리소스에 대해 복수 개의 Lock에 의해 처리가 지연되는 현상이다. 

대표적으로 Critical Section 에 접근하여 공유 자원을 처리하는 여러개의 로직이 서로에 대한 의존도(Dependency)를 가질 때 발생한다.


다음과 같은 상황을 가정해보자.




위의 그림에서 프로세스1 은 Resource 1을 선점하고 있으며, Resource 2에 대한 작업처리를 요구한다.

Resource 2에 대한 처리를 완료하면 Resource 1을 사용할 수 있도록 Critical Section 바깥으로 반환할 것이다.

반대로 프로세스2 는 Resouce 2를 선점하고 있으며 Resource 1에 대한 작업 처리를 마친 후 Resource 2를 반환할 것이다.


위의 상황에서 두 프로세스는 서로 선점하는 Resource가 다르므로 동시에 Critical Section 진입이 가능하지만, 서로의 자원을 요구하는 탓에 탈출은 불가능하다. 이 상황을 Deadlock이라고 한다.


Deadlock 이 발생하면 자원의 누수 및 동작의 교착상태가 계속되기 때문에 어플리케이션 또는 시스템에 치명적이며 따라서 문제 해결을 위해 다음과 같이 관리 한다.


- 교착 상태의 예방

(1) Mutual Exclusion 조건 제거

(2) 사용할 때에만 해당 자원을 점유하고 사용하지 않을 때에는 해당 자원을 다른 프로세스가 사용할 수 있도록 양도

(3) 선점 가능한 프로토콜 제작

(4) 자원 접근에 대한 순차적 처리 


- 교착 상태의 회피

 : 자원 요청에 대해 Circular Wait를 방지하기 위한 할당 상태를 검사한다.


- 교착 상태의 무시

 : 확률이 낮은 경우 별도의 처리를 하지 않는다.



+ Recent posts