동시성 제어: 락(Lock)

bien·2025년 6월 9일
0

java

목록 보기
12/12

🔒 락(Lock) 정의

  • 여러 스레드나 프로세스가 동시에 공유 자원(데이터, 객체, 파일 등)에 접근하여 발생하는 문제를 막기 위한 동기화 메터니즘
    • 특정 시점에는 오직 허가된 쓰레드/프로세스만이 자원에 접근하도록 제어하는 기술

잠금 대상 범위(Scope)

  • 데이터베이스 락 (Database Locks):
    • 데이터베이스 관리 시스템(DBMS)이 데이터의 일관성과 무결성을 지키기 위해 제공하는 락.
    • 여러 트랜잭션이 동시에 같은 데이터(행, 테이블 등)에 접근하는 것을 제어한다
    • 트랜잭션의 격리 수준(Isolation Level)에 따라 자동으로 작동하거나, SELECT ... FOR UPDATE(행에 대한 배타적 잠금) 또는 LOCK TABLES (테이블 잠금) 같은 명령어로 명시적으로 사용할 수 있다.
    • 주로 DB 데이터의 정합성을 보장하는 데 사용된다.
  • 애플리케이션 락 (Application Locks):
    • 애플리케이션 코드 레벨에서 특정 자원(객체, 데이터 구조, 코드 블록 등)에 대한 동시 접근을 제어하기 위해 사용
    • Java에서는 synchronized 키워드나 java.util.concurrent.locks 패키지의 클래스(ReentrantLock 등)를 사용한다.
    • 주로 메모리 상의 공유 데이터나 특정 로직의 동시 실행을 제어하는 데 사용된다.

잠금 전략(Locking Strategy)

  • 비관적 잠금(Pessimistic Locking):
    • "충돌이 발생할 것이다"라고 가정하고, 실제 데이터에 접근하기 전에 미리 락을 거는 방식.
    • 데이터를 수정하는 동안 다른 접근(읽기 포함 또는 쓰기만)을 막는다.
    • 장점: 데이터 정합성을 확실하게 보장할 수 있다. 구현이 비교적 간단할 수 있다.
    • 단점:
      • 실제 충돌이 거의 없어도 락을 걸기 때문에 성능 저하(대기 시간 증가)가 발생할 수 있다.
      • 락을 너무 오래 잡고있으면 시스템 전체의 처리량(Throughtput)이 떨어질 수 있다.
      • 데드락(교착 상태) 발생 가능성이 있다.
    • 예시:
      • DB의 SELECT ... FOR UPDATE, Java의 synchronized, ReentrantLock.
  • 낙관적 잠금(Optimistic Locking)
    • "충돌이 거의 발생하지 않을 것이다"라고 가정하고, 일단 락 없이 작업을 수행한 뒤, 마지막에 데이터를 업데이트 할 때 충돌 여부를 검사하는 방식.
    • 주로 데이터에 버전(Version) 컬럼을 두고, 데이터를 읽을 때 버전 정보도 함께 읽는다. 업데이트 시에는 읽었던 버전과 현재 DB의 버전이 일치하는지 확인하고, 일치할 때만 업데이트를 수행한다. 버전이 다르면 다른 트랜잭션이 먼저 수정한 것이므로, 업데이트를 실패시키고 재시도하거나 사용자에게 알린다.
    • 장점:
      • 락으로 인한 대기 시간이 없어 동시처리 성능이 높다.
      • 데드락 발생 가능성이 거의 없다.
    • 단점:
      • 충돌이 발생하면 롤백 및 재처리 로직이 필요하여 구현이 복잡해질 수 잇다.
      • 충돌이 빈번하게 발생하면 오히려 비관적 락보다 성능이 나빠질 수 있다.
    • 예시:
      • JPA의 @Version 애노테이션, 직접 버전 컬럼을 비교하는 로직, Compare-And-Swap(CAS)알고리즘 (Java의 Atomic*클래스에서 사용)

잠금 유형 (Sharing Type)

  • 공유 잠금 (Shared Lock/Read Lock):
    • 데이터를 읽기 위해 사용하는 락
    • 여러 스레드/트랜잭션이 동시에 공유 락을 획득할 수 있다. 즉, 여러명이 동시에 읽는 것은 허용된다.
    • 하지만 공유 락이 걸려있는 동안에는 아무도 해당 자원에 배타적 잠금(Write Lock)을 걸 수 없다.
      • 즉, 읽고 있는 동안에는 다른 스레드가 수정할 수 없다.
    • 예시: DB의 읽기 락(일부 격리 수준에서 사용), Java의 ReadWriteLock의 읽기 락.
  • 배타적 잠금 (Exclusive Lock/Write Lock):
    • 데이터를 쓰기(수정/삭제) 위해 사용하는 락
    • 오직 하나의 스레드/트랜잭션만이 배타적 락을 획득할 수 있다.
    • 배타적 락이 걸려있는 동안에는 어떤 스레드/트랜잭션도 해당 자원에 공유 락이나 배타적 락을 걸 수 없다.
      • 즉, 쓰고 있는 동안에는 다른 스레드가 쓸 수 없다.
    • 예시: DB의 쓰기 락(UPDATE, DELETE 시 또는 SELECT ... FOR UPDATE), Java의 synchronized, ReentrantLock, ReadWriteLock의 쓰기 락.

Java 애플리케이션의 락 구현 방식

  • 내재적 잠금/모니터 락(Instrinsic Lock/Monitor Lock):
    • Java의 모든 객체는 고유한 내재적 잠금(모니터)을 가지고 있다.
    • synchronized 키워드를 사용하여 이 락을 이용한다 (synchronized(객체) 또는 synchronized 메소드)
    • 사용하기 간편하지만, tryLock, 락 획득 시도 중단 등 세밀한 제어는 어렵다.
  • 명시적 잠금/재진입 가능 잠금(Explicit Lock/Reentrant Lock):
    • java.util.concurrent.locks 패키지에서 제공하는 락(ReentrantLock, ReadWriteLock 등)
    • lock(), unlock(), tryLock() 등의 메소드를 직접 호출하여 락을 제어한다.
      반드시 finally 블록에서 unlock()을 호출하여 락을 해제해야 한다.
    • 내재적 잠금보다 더 다양한 기능(예: 공정성 설정, 타임아웃 설정, 인터럽트 가능한 락 획득 시도)을 제공한다.
    • ReentrantLock은 이름처럼 재진입(Reentrant)이 가능하다. 즉, 락을 이미 획득한 스레드가 해당 락을 다시 획득하려고 할 때 대기 없이 바로 획득할 수 있다. (synchronized 도 재진입 가능)

분산 환경에서의 잠금

  • 분산 잠금(Distributed Lock):
    • 여러 서버 인스턴스(마이크로서비스 환경 등)에서 공유된 자원(예: 특정 DB 레코드에 대한 작업 권한, 외부 API 호출 제한 등)에 대한 동시 접근을 제어하기 위해 사용된다.
    • 애플리케이션 인스턴스 외부의 공유 저장소(예: Redis, ZooKeeper, Consul)나 데이터베이스 테이블을 이용하여 락 상태를 관리한다.
    • 구현 및 관리가 복잡하며, 락 관리 시스템 자체의 안정성과 성능이 중요하다.

profile
Good Luck!

0개의 댓글