✅ 1. synchronized (Java 기본 동기화)
📌 원리
• 자바의 기본 동기화 방식으로, 한 번에 하나의 스레드만 enterRoom() 메소드 실행 가능.
• 메소드 자체에 synchronized 키워드를 붙이거나 특정 객체에 대해 동기화 블록을 사용할 수 있음.
@GetMapping("/room/{roomId}")
public synchronized ModelAndView enterRoom(@PathVariable Long roomId, @LoginUser LoginUserRequest loginUserRequest) throws IllegalAccessException {
// ... codes
}
✅ 장점
• 간단하고 구현이 쉬움 (synchronized 키워드만 추가).
• 자바 기본 제공 기능이므로 별도의 라이브러리 불필요.
• JVM 수준에서 관리되므로 안정적.
❌ 단점
• 블로킹 방식이므로, 하나의 스레드만 enterRoom()을 실행 가능 → 성능 저하.
• 전체 메소드가 동기화됨 → 불필요한 요청까지 대기하게 됨.
• 분산 서버 환경에서는 적용 불가능 (싱글 인스턴스에서만 유효).
✅ 2. ReentrantLock (Java 동기화, 더 세밀한 제어)
📌 원리
• synchronized와 유사하지만, 더 정교한 동기화 제어 가능.
• 공정 락 (fair lock) 설정 가능 → 먼저 요청한 스레드가 우선 처리됨.
• 시도하다 실패하면 다른 작업을 수행할 수 있음 (tryLock 지원).
import java.util.concurrent.locks.ReentrantLock;
@Component
public class RoomLockManager {
private final Map<Long, ReentrantLock> roomLocks = new ConcurrentHashMap<>();
public ReentrantLock getLock(Long roomId) {
return roomLocks.computeIfAbsent(roomId, id -> new ReentrantLock());
}
}
@GetMapping("/room/{roomId}")
public ModelAndView enterRoom(@PathVariable Long roomId, @LoginUser LoginUserRequest loginUserRequest) throws IllegalAccessException {
ReentrantLock lock = roomLockManager.getLock(roomId);
lock.lock();
try {
// codes...
} finally {
lock.unlock();
}
}
✅ 장점
• 동기화 범위를 세밀하게 조정 가능 (lock() / unlock() 명시적 제어).
• tryLock()을 사용하면 대기하지 않고 실패 시 다른 작업 수행 가능.
• fair 모드를 사용하면 먼저 요청한 스레드가 우선 처리됨 (기아 문제 방지).
❌ 단점
• synchronized보다 코드가 길어지고 복잡.
• 락을 잘못 해제하면 데드락 발생 가능.
• 싱글 서버에서만 동작, 분산 환경에서는 사용 불가.
✅ 3. Redis Lock (분산 환경에서 강력한 동기화)
📌 원리
• Redis를 이용해 분산 락을 생성 → 여러 서버에서도 동시에 실행되지 않도록 제어 가능.
• SET NX(Not Exists) 옵션을 사용하여 이미 락이 존재하면 요청을 무시.
• TTL(Time To Live) 설정 가능 → 일정 시간이 지나면 자동 해제.
import org.redisson.api.RedissonClient;
import org.redisson.api.RLock;
import java.util.concurrent.TimeUnit;
@Service
public class RoomService {
private final RedissonClient redissonClient;
public RoomService(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
public ModelAndView enterRoom(Long roomId, LoginUserRequest loginUserRequest) throws IllegalAccessException {
RLock lock = redissonClient.getLock("room:" + roomId);
try {
if (lock.tryLock(10, 5, TimeUnit.SECONDS)) { // 10초 대기, 5초 유지
// codes...
} else {
throw new RuntimeException("다른 사용자가 방 입장 중입니다.");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("입장 중 오류 발생");
} finally {
lock.unlock();
}
}
}
✅ 장점
• 분산 환경에서도 적용 가능 → 여러 서버에서 동일한 데이터 동기화 가능.
• TTL 설정 가능 → 만약 서버가 다운되면 자동으로 락 해제됨.
• 과도한 대기 방지 가능 → tryLock()을 사용해 특정 시간 동안만 대기.
❌ 단점
• Redis 서버가 필요함 (설치 및 설정 필요).
• lock()을 놓치면 다른 요청이 무한 대기할 가능성 있음.
• 일반적인 synchronized나 ReentrantLock보다 속도가 느림 (네트워크 오버헤드).
💡 어떤 방법이 가장 효율적일까?
방법 | 특징 | 장점 | 단점 | 추천 상황 |
synchronized | JVM 기본 락 | 간단함, 빠름 | 블로킹, 단일 서버 전용 | 동시 요청이 적고 단일 서버 |
ReentrantLock | 더 세밀한 동기화 | 락 세부 제어 가능, fair lock | 코드 복잡, 데드락 위함 | 멀티스레드 환경에서 부분 동기화 필요 |
Redis Lock | 분산 환경 동기화 | 여러 서버 지원, TTL 가능 | Redis 필요, 네트워크 오버헤드 | 다중 서버에서 경쟁이 많은 경우 |
- 결론 : 로컬에서 1번과 2번을 적용해서 테스트 하고, 3번을 적용해서 배포할 예정이다.
'Project > SpringBoot' 카테고리의 다른 글
레디스 사용 (0) | 2025.05.13 |
---|---|
자바의 동시성 처리 방법 (3) | 2025.05.05 |
NginX를 적용한 로드밸런싱 적용 및 웹 소켓 로드 밸런싱 (0) | 2025.01.31 |
AWS 프로젝트 배포과정 (0) | 2025.01.31 |
깃허브 액션에 대해서 (0) | 2025.01.30 |