Java 객체의 Lock 이해하기
[Lock 은 무엇이며 왜 필요한가?]
락은 쓰레드 동기화(synchronization) 를 위해 필요하다.
- 쓰레드 동기화: 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 막는 것
프로세스가 싱글쓰레드인 경우 프로세스 내에서 단 하나의 쓰레드만 작업하기 때문에 프로세스의 자원을 가지고 작업하는데 별 문제가 없지만, 멀티쓰레드인 경우 여러 쓰레드가 같은 프로세스 내 자원을 공유해서 작업하기 때문에 서로의 작업에 영향을 주게 된다. 예를 들어 쓰레드A 가 작업하던 공유데이터를 쓰레드B가 임의로 변경했다면, 쓰레드 A가 다시 제어권을 받아 나머지 작업을 마쳤을 때 원래 의도했던 결과와 다른 결과를 얻는 동기화문제가 발생한다. 이러한 일이 발생하는 것을 방지하기 위해 한 쓰레드가 특정 작업을 끝마치기 전까지 다른 쓰레드에 의해 방해받지 않도록 하는 것이 필요하다. 그래서 도입된 개념이 임계영역(critical section) 과 잠금(lock) 이다.
Race Condition
- 공유자원에 동시에 접근하여 결과값에 영향을 줄 수 있는 상태
Critical Section
- 여러 개의 쓰레드 또는 프로세스가 동시에 실행될 경우, 동시에 접근하면 안 되는 공유 자원을 사용하는 코드 영역
- 한 번에 하나의 쓰레드만 실행할 수 있어야 하며, 데이터 일관성을 보장해야 한다.
Lock
- 임계 영역(Critical Section)에 한 번에 하나의 쓰레드만 접근하도록 제한하는 동기화 기법 중 하나
[Java 객체에서 Lock 은 어디에 있는가?]
JVM 에서 모든 자바 객체는 헤더(Header)라는 메타데이터를 포함하고 있다. 객체의 헤더에는 JVM이 객체를 관리하는 데 필요한 정보가 저장된다. 이 중 Lock 정보를 담고 있는 헤더 부분을 Mark word 라고 한다.
따라서, java 객체에서의 Lock은 객체의 헤더(header)에 논리적으로 포함된 데이터로, 힙 메모리 내의 객체에 속하는 데이터의 일부다.
Java 객체의 Monitor 이해하기
[자바의 Monitor 란 무엇인가?]
OS 에서 Monitor 는 Lock 보다 더 높은 수준의 추상화로 critical section에 한 번에 하나의 쓰레드만 들어가도록 프로그래밍 언어에서 제공하는 동기화 기법을 의미한다. 자바의 모든 객체는 모니터를 가지며, 객체의 락을 활용하여 스레드 동기화를 제공한다. 모든 객체가 갖고 있으니 고유 락(intrinsic lock), 모니터처럼 동작한다고 하여 모니터 락(monitor lock) 또는 모니터(monitor)라고도 부르기도 한다.
어떤 쓰레드가 객체의 인스턴스 변수를 수정하려면, 해당 객체의 락을 소유해야 한다.(즉, 락 메모리 영역에 특정 플래그를 설정해야 한다.) 다른 쓰레드가 동일한 객체를 수정하려고 하면, 기존의 락 소유 쓰레드가 락을 해제할 때까지 대기해야 한다. 자바 객체는 모니터를 활용해 락 획득 및 반납을 처리하도록 내부적으로 구현되어 있으며, 상호 배제(Mutual Exclusion) 와 협력(Cooperation) 이라는 두 가지 동기화 기능을 제공한다.
상호 배제(Mutual Exclusion)
상호 배제는 여러 스레드가 동시에 공유 자원에 접근하지 못하도록 제한하여 데이터의 일관성과 안전성을 보장하는 동기화 메커니즘이다.
자바에서는 객체가 가지고 있는 모니터를 통해 이를 보장하며, synchronized 키워드를 사용하면 JVM이 자동으로 뮤텍스(Mutex)기반의 동기화를 처리한다. synchronized 키워드는 메서드나 코드 블록에 적용 가능하며, 해당 블록을 실행하려면 해당 객체의 모니터를 획득해야 한다.
- 모니터를 획득한 스레드만 임계영역(Critical Section)에 접근 가능
- 다른 스레드는 락이 해제될 때까지 대기 상태로 차단
- synchronized 블록이 종료되면 모니터 락이 해제되며, 대기 중인 스레드 중 하나가 락을 획득하고 임계 영역을 실행
이러한 방식으로 한 번에 하나의 스레드만 공유 데이터에 접근할 수 있도록 보장하는 것이 상호 배제이다.
협력(Cooperation)
협력은 스레드 간 협업을 통해 특정 조건을 만족할 때까지 실행을 제어하기 위한 동기화 메커니즘이다. 자바에서는 모니터의 조건 변수(Condition Variable) 를 활용하여 협력을 구현하며, Object 클래스의 native 메서드로 구현되어 있는 wait(), notify(), notifyAll() 메서드를 사용한다.
- wait(): 특정 조건이 만족되지 않으면 현재 스레드는 락을 해제하고 대기 셋(WaitSet)에서 대기
- notify(): 조건이 충족되었을 때, 대기 중인 스레드 중 하나를 깨워 다시 실행
- notifyAll(): 대기 중인 모든 스레드를 깨워 실행할 기회를 부여
[Monitor 는 어떻게 동작하는가?]
자바의 모니터는 EntrySet(진입셋) 과 WaitSet(대기셋) 이라는 대기 자료구조를 가진다.
EntrySet
- 모니터의 락을 얻기 위해 대기 중인 스레드의 집합
- 특정 스레드가 락을 사용 중이라면, 다른 스레드는 EntrySet에 들어가 대기
- 락이 해제되면 EntrySet 중 하나의 스레드가 락을 획득하고 실행됨
WaitSet
- 조건 변수를 사용하여 특정 조건이 충족될 때까지 대기하는 스레드의 집합
- 모니터를 소유하고 있는 쓰레드가 wait() 메서드를 호출하면 락을 해제한 후, WaitSet에서 대기
- notify() 또는 notifyAll()이 호출되면 WaitSet에서 깨어난 스레드는 EntrySet으로 이동하여 락을 다시 획득하기 위해 경쟁
간단히 정리하면, 쓰레드가 Entry Set 으로 진입하면서 바로 Monitor Lock 획득을 시도한다. 만약 다른 쓰레드가 이미 획득했다면 해당 쓰레드는 다시 Entry Set 에서 대기하고, 성공적으로 Monitor Lock 을 획득한다면 임계 영역에서 작업 후 Lock 을 Release 한다.
References
https://www.geeksforgeeks.org/monitors-in-process-synchronization/
https://howtodoinjava.com/java/multi-threading/multithreading-difference-between-lock-and-monitor/
'Java' 카테고리의 다른 글
[Java] synchronized block 이해하고 사용하기 (0) | 2025.03.09 |
---|---|
[Java] Blocking Queue 이해하고 사용해보기 (1) | 2025.03.07 |
[Java] Callable, Feature 이해 및 사용예시 (0) | 2025.02.26 |
[Java] 열거형(Enum) 이해하고 사용하기 (1) | 2025.02.22 |
[Java] Thread, Runnable 이해하고 사용하기 (1) | 2025.02.21 |