728x90
스레드 동기화
스레드 동기화 개념
- 스레드 동기화 : 멀티 스레드 환경에서 여러 스레드가 한 공유 자원에 동시에 접근하지 못하도록 막는 것
- 임계 영역 : 공유 데이터가 사용되어 동기화가 필요한 부분
- 상호 배제 : 임계 영역이 오직 한 스레드에 의해 배타독점적으로 사용되도록 하는 기술
- 임계 영역에 먼저 진입한 스레드가 임계 영역의 실행을 끝낼 때까지 다른 스레드가 진입하지 못하도록 보장
스레드 동기화의 필요성
- 예시 : 재고가 1개인 제품에 대해 동시다발적으로 주문이 10건 인입되었을 때, 남은 재고가 1개임에도 불구하고 주문 10건이 모두 성공하는 상황이 발생
→ 공유 데이터가 훼손되는 문제 발생
synchronized 키워드
- Synchronized (임계 영역 설정과 락 권한)
public synchronized void insertOrder(OrderDto orderDto) {...}
- 메서드 전체를 임계 영역으로 설정
- 임계 영역으로 지정할 메서드의 반환 타입 앞에 `synchronized` 키워드 추가
- 설정한 메서드가 호출되었을 때 메서드를 실행할 스레드는 메서드가 포함된 객체의 락 (Lock) 을 획득
- 다시 락이 반납되기 전까지 다른 스레드는 해당 스레드를 실행하지 못함
public void insertOrder(OrderDto orderDto) {
synchronized (this) {
// ...
}
}
- 특정 코드 블록을 임계 영역으로 설정
- 임계 영역으로 지정할 코드 상단에 `synchronized` 키워드 추가
- 예제처럼 소괄호 내에 해당 영역이 포함된 객체의 참조 입력 시 참조 변수 객체의 락 (Lock) 을 사용하게 됨
private final Object lock = new Object();
public void insertOrder1(OrderDto orderDto) {
synchronized (lock) {
// ...
}
}
public void insertOrder2(OrderDto orderDto) {
synchronized (lock) {
// ...
}
}
- 예제처럼 각각 다른 메서드에서도 한번에 한 스레드만 진입가능해야 할 경우 락 (Lock) 객체를 지정하여 사용할 수 있음
- 동시에 `insertOrder1` 에 진입한 스레드와 `insertOrder2` 에 진입한 스레드가 있을 경우 임계 영역을 먼저 진입한 스레드가 작업을 마칠 때까지 다른 스레드는 대기 상태가 됨
private final ConcurrentHashMap<String, Object> lockMap = new ConcurrentHashMap<>();
public void insertOrder1(OrderDto orderDto) {
Object lock = lockMap.computeIfAbsent(key, k -> new Object());
synchronized (lock) {
// ...
}
}
public void insertOrder2(OrderDto orderDto) {
Object lock = lockMap.computeIfAbsent(key, k -> new Object());
synchronized (lock) {
// ...
}
}
- 특정 키 값에 대해 동일한 값으로 메서드에 진입하는 경우에만 락 (Lock) 을 걸어야 할 경우
- `ConcurrentHashMap` 타입의 map 에 해당 키 값이 존재하는 경우에는 대기
- 키가 존재하지 않는 경우에는 새로운 Object 객체를 만들고 진입
ReentrantLock
- ReentrantLock : 특정 시간을 대기하다 무시하고 지나가거나 Lock 잡는 순서를 보장해야 하는 경우 등 Lock에 대해 다양하게 관리하거나 컨트롤해야 할 경우에 사용
- 사용방법
// Constructor
ReentrantLock()
ReentrantLock(boolean fair)
- fair 을 true 로 설정 시 가장 오래 기다린 스레드가 Lock 을 얻을 수 있도록 공정하게 처리
- 단, 스레드 대기 시간을 확인하는 과정이 필요하므로 성능이 저하될 수 있음
// Method
void lock() // lock 잠금
void unlock() // lock 해제
boolean isLocked() // lock이 잠겼는지 확인
boolean tryLock() // lock polling
boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
- `synchronized` 블럭과 달리, 수동으로(명시적으로) Lock 을 잠그고 해제해야 함
@Synchronized 어노테이션
private static final Object LOCK = new Object();
@Synchronized("LOCK")
public void insertOrder() {...}
@Synchronized("LOCK")
public void updateOrder() {...}
@Synchronized
public void deleteOrder() {...}
- `@Synchronized` : 메소드에 사용되는 어노테이션으로 기본적으로 지원되는 `synchronized` 키워드보다 더 세세한 설정이 가능
- `synchronized` 키워드는 static 혹은 instance 단위로 락을 잠금
- `@Synchronized` 어노테이션은 파라미터로 입력받는 Object 단위로 락을 잠금
- 파라미터로 아무것도 입력하지 않으면 어노테이션이 사용된 메소드 단위로 락
- `insertOrder` 와 `updateOrder` 는 두 메소드가 동시에 호출되었을 경우 한 번에 한 스레드만이 메소드에 진입할 수 있고 다른 스레드는 대기해야함
- `deleteOrder` 는 두 스레드가 동시에 호출했을 경우 한 번에 한 스레드만이 진입할 수 있음
728x90
'Java | Spring' 카테고리의 다른 글
| [Spring] @ControllerAdvice, @RestControllerAdvice (0) | 2025.03.09 |
|---|---|
| [Spring] @RequestParam, @RequestBody, @ModelAttribute (1) | 2025.03.09 |
| [Java] 제네릭 (Generics) (2) | 2025.03.08 |
| [Java] 다형성과 캐스팅 (0) | 2025.03.08 |
| [Java] Builder 패턴과 @Builder 어노테이션 (0) | 2025.03.07 |