자바 동시성을 활용한 예약 시스템 구현

Dope
7 min readMar 22, 2020

--

서론

개발을 하다보면 예약 프로그램 구현이 필요한 경우가 있습니다. 예를 들면 시설물 사용 신청, 단체 예약, 영화 예약 등 이 있는데.. 예약 시스템의 특징은 여러 사람이 동시에 신청할 가능성이 있다는 것입니다. 이 부분이 핵심인데, 최악의 경우는 정원이 80명인데 81명이 들어오는 경우.. 즉, 예약 정원보다 초과되는 경우입니다. 이런 현상을 막기 위해서는 자바의 동시성에 대해서 잘 알고 활용하면 동시에 신청한 사용자들에 대해서 순차적으로 처리할 수 있습니다.

먼저 예제를 소개하고 어떻게 구현되는지에 대해서 먼저 다루고, 자바 동기화를 위해 언제 synchronized 를 사용하며 이를 대체할 방법은 어떤것이 있는지에 대해서 다루겠습니다.

예제(Example)

시설물 사용신청 프로그램을 만든다고 가정하겠습니다. 시설물(체육관, 강당, 본관, 도서관 등)은 관리자가 관리자 페이지에서 신청할 수도 있고, 사용자가 사용자 페이지에서 신청할 수도 있습니다.

시설물은 체육관, 2020–03–22, 08:00 ~ 09:00 처럼 특정 시설물, 특정 날짜, 특정 시간대에 한 사람만 신청이 가능합니다.

  • 시설물명(title)
  • 신청 날짜(applyDate)
  • 시설물 사용 시작시간(startTime)
  • 시설물 사용 종료시간(endTime)

위 4가지 조건을 검증 조건(Validation Condition) 이라부르겠습니다.

그렇다면 우리가 만들어야 할것은, 사용자 혹은 관리자가 신청하려는 시설물, 날짜, 시작시간, 종료시간에 대해서 이미 등록된 신청 내역이 있는지 검증 조건을 검증하는 로직이 필요합니다.

고려해야할 사항은 다음과 같습니다. 관리자, 사용자 둘 다 동시에 신청할 가능성이 있습니다. 즉, 관리자가 체육관 2020–03–22 , 08:00 ~ 09:00 에 신청하는 순간, 사용자 2명 혹은 그 이상이 동시에 관리자와 같은 시간에 같은 조건으로 신청할 가능성이 있다는 얘기 입니다. 그러면 이러한 경우를 중복 되지 않게 어떻게 처리해야할지 알아봅시다.

중복이 되지 않게 막는 방법

첫 번째 방법은 DataBase 에서 막는 방법입니다. 검증 조건에 대해서 테이블 컬럼에 Unique Key 를 지정하는 방법과 Lock 을 거는 방법이 있습니다. 이 방법은 예약 프로그램마다 성향이 다르기 때문에 Unique Key 를 지정할 수 있는지에 대해서 먼저 판단을 하고, 상황이 그렇지 못한경우는 무조건 서버단에서 막아야 합니다.

두 번째 방법은 서버단(Java)에서 막는 방법인데, 첫 번째 방법을 실시 하더라도 두 번째 방법도 같이 해줘야 한다고 생각합니다.

검증 로직 코드는 아래와 같습니다.

중요한것은, 해당 로직이 관리자 프로젝트에도 들어가 있고, 사용자 프로젝트에도 들어가 있습니다. 하지만 우리가 고려해야할 것은 관리자와 사용자 모두 동시에 신청할 가능성이 있기 때문에, 해당 로직을 관리자 프로젝트에 관리하게 하며, 사용자가 신청할 경우 검증 로직을 관리자 프로젝트의 위 메서드를 거치게 만들어야 합니다.

사용자 페이지의 FacilityApplyServiceImpl 클래스에서 RestTemplate 를 이용하여 관리자 페이지와 통신을 할 수 있습니다.

해당 메서드를 통해서 관리자 CMS 에 있는 restApiUrl 경로와 일치하는 핸들러 메서드(Hanlder Method) 를 찾습니다.

여기서 중요한 점은, 관리자 핸들러 메서드와 통신하는 URL 주소가 도메인 네임일 경우 만약 HTTPS 면 운영 서버에서 통신이 안될 수 도 있습니다. 이런 이슈가 발생하면, 관리자 핸들러 메서드와 통신하는 URL 을 IP 주소로 바꾸면 정상적으로 동작합니다.

이제 해당 핸들러 메서드를 통해서 사용자는 관리자에서 관리하는 검증 로직을 거치게 됩니다. 관리자 또한 별도의 핸들러 메서드를 통해서, 사용자와 같은 검증로직을 거치게 되므로, 관리자 사용자의 신청이 동시에 들어와도 동기화(synchronized) 를 통해서 순차적으로 처리하므로, 중복을 막을 수 있게 되는 것입니다. 하지만 isAvailableCreate뿐만 아니라 추가적으로 동기화 블럭을 설정해줘야 하는 곳이 있는데, 관리자에서 isAvailableCreate메서드를 호출하는 메서드와, 사용자에서 isAvailableCreateCommunicationWithCms를 호출하는 메서드 입니다. 왜냐하면 여러 스레드가 공유 객체의 값을 변경 시킬 가능성이 있기 때문입니다.

그럼 아래에서는 언제 synchronized를 사용하고, 동시성을 처리하기 위한 다른 방법은 무엇이 있는지에 대해서 알아보겠습니다.

자바의 동시성(Concurrency)

동시성은 한 프로그램 내에서 실행되는 여러 스레드가 객체를 공유하는 경우 입니다. 스프링에서는 요청 1개당 하나의 스레드를 사용하기 때문에, 여러 요청이 오는경우 스레드도 여러개가 생깁니다. 따라서 자바에서 동기화를 통해 동시성을 처리하지 않으면 검증 로직을 뚫고 DB로 데이터가 입력되어 중복이 발생할 수 있게되는 것입니다.(DB에서 Lock 혹은 Unique Key로 중복 처리 하지 않은 경우)

자바는 synchronized 키워드를 통해 배타 동기를 제공합니다. 배타 동기란 하나의 스레드만 공유자원에 접근하게 해준다. 공유자원을 사용하는 스레드가 존재하면, 베타동기에서 대기하게 하는것을 의미합니다.

synchronized 는 마치 줄을 세워 처리하는 것과 같으므로 무분별하게 사용할 경우 프로그램 성능이 떨어지게 됩니다. synchronized 의 특징은 불공정 방법(unfair) 이라 해서 스레드간 락(lock)을 획득하는 순서를 보장해 주지 않습니다.

이와 유사한 스레드 동기화 매커니즘이 존재하는데 java.util.concurrent에 존재하는 락(Lock) 입니다. 그 중에서 소개해드릴 것은 ReentrantLock입니다. ReentrantLock 은 공정 방법과 불공정 방법으로 설정을 할 수 있습니다. 공정 방법으로 설정하고 싶은 경우 생성자에 Boolean true 값을 넘겨서 초기화 하면 됩니다.

자바 1.5 이전에는 synchronized 성능이 더 좋았지만 그 이후에는 차이가 거의 없으며, 만약 스레드가 동시에 들어오는 경우가 4개 이상인 경우 혹은, 락을 모니터링 하는 경우, 동기화 코드가 복잡한 경우에는 ReentrantLock 효율이 더 좋으며, 단순한 동기화 코드에는 동기화 블럭을 사용하는 편이 낫습니다.

References.

--

--

Dope

Developer who is trying to become a clean coder.