자바의 ReentrantLock 가볍게 알아보기!

2024. 2. 28. 21:42· 언어/JAVA
목차
  1. 공정한 락과 비공정한 락
  2. 잠금 방식
  3. 1. lock()
  4. 2. tryLock()
  5. tryLock() 사용 시 주의점
728x90

자바에서는 스레드 간 동기화 작업을 지원하기 위해 synchronized와 ReentrantLock을 지원한다.

 

그 중 ReentrantLock은 synchronized와 비교해 조금 더 유연한 락 획득 및 관리 방식을 지원하고 있다.

 

기본적인 작동 방식을 익혀보자.

 

공정한 락과 비공정한 락

ReentrantLock 인스턴스를 생성할 때는 fair 라는 인자를 설정해 락 획득을 "공정"하게 처리할지 아닐지를 설정할 수 있다.

 

여기서 "공정"하다는 의미는 락 획득을 대기 중인 스레드들에 대하여 가장 오래 대기한 스레드에게 먼저 락을 점유할 수 있도록 우선권을 준다는 의미이다.

생성자에서 볼 수 있다시피, fair 인자에 따라 아예 다른 인스턴스를 생성해주는 것을 확인할 수 있다.

 

인자가 없는 기본 생성자도 제공하는데, 기본 생성자에선 fair를 false로 간주하고 NonFairSync() 인스턴스를 반환한다.

 

잠금 방식

ReentrantLock은 잠금을 위해 크게 두 가지 메서드를 지원한다.

1. lock()

기본적인 잠금 방식이다.

 

만약 lock() 호출 시 다른 스레드에서 락을 소유 중이라면 현재 스레드는 잠시 블락되고 락을 획득할 수 있을 때까지 대기한다.

 

synchronized를 사용했을 때와 유사한 형태라고 이해하면 될 것 같다.

 

사용법은 아래 코드를 참고할 수 있다.

public class ReentrantLockBookCountService {

    private final ReentrantLock reentrantLock = new ReentrantLock();
    private int count;
    
    public void decreaseCount() {
        try {
            reentrantLock.lock();
            this.count -=1;
        } finally {
            reentrantLock.unlock();
        }
    }
}

2. tryLock()

tryLock()은 lock()과 다르게 스레드가 락 획득을 위해 대기하는 시간을 타임아웃으로 지정할 수 있다.

 

이에 따라서 지정된 시간동안 락 획득에 성공했는지 여부를 리턴한다.

따라서 tryLock()을 적절하게 사용하면 스레드가 락 획득을 지나치게 오래 기다리는 상황을 막을 수 있고, 락 획득 실패 시 다양한 처리 전략을 적용하는 것이 가능해진다.

 

tryLock()을 사용하는 예시는 아래 코드와 같다.

public void decreaseCount() {
        if (reentrantLock.tryLock(5, TimeUnit.SECONDS)) { // 5초만 동안 락 획득 시도
            try {
                this.count += 1;
            } finally {
                reentrantLock.unlock();
            }
        } else {
            // 락 획득 실패 시 처리 작업
        }
    }
```

 

만약 타임아웃 값을 지정하지 않고 메서드를 호출하면 락 획득 시도 시 다른 스레드가 락을 점유 중이라면 곧바로 실패처리하고 false를 리턴할 것이다.

 

따라서, 동일한 테스트 코드를 돌렸을 때 tryLock() 방식은 lock() 방식과 다르게 간헐적으로 테스트에 실패하는 것을 확인할 수 있다.

@Test
void decreaseTest() throws InterruptedException {
    bookCountService.setCount(100);

    Runnable decreaseCount = () -> bookCountService.decreaseCount();

    concurrentTest(100, decreaseCount);

    assertThat(bookCountService.getCount()).isEqualTo(0);
}

void concurrentTest(int executeCount, Runnable methodToTest) throws InterruptedException {
    ExecutorService executorService = Executors.newFixedThreadPool(32);
    CountDownLatch countDownLatch = new CountDownLatch(executeCount);

    for (int i = 0; i < executeCount; i++) {
        executorService.submit(() -> {
            methodToTest.run();
            countDownLatch.countDown();
        });
    }

    countDownLatch.await();
}

tryLock() 사용 시 주의점

tryLock() 메서드에 적힌 자바독을 확인해보면 아래와 같은 문구를 확인할 수 있다.

 * Even when this lock has been set to use a
 * fair ordering policy, a call to {@code tryLock()} <em>will</em>
 * immediately acquire the lock if it is available, whether or not
 * other threads are currently waiting for the lock.
 * This &quot;barging&quot; behavior can be useful in certain
 * circumstances, even though it breaks fairness. If you want to honor
 * the fairness setting for this lock, then use
 * {@link #tryLock(long, TimeUnit) tryLock(0, TimeUnit.SECONDS)}
 * which is almost equivalent (it also detects interruption).

간단하게 해석해보자면, tryLock()은 락이 공정한 순서로 획득되도록(fair ordering policy) 설정되었더라도 이와 상관없이 락 획득이 가능하다면 다른 대기 중인 스레드들을 무시하고 락을 획득하게 할 수 있다.

 

이는 공정성을 어기는 작동방식이지만 특정 상황에 유용할 수 있다.

 

만약 tryLock()을 사용하면서 공정성을 함께 유지하고자 한다면 타임아웃을 인자로 받는 생성자를 통해 인스턴스를 생성해야한다. (타임아웃은 0초로도 설정 가능하다!)

 

결론적으론, fair 파라미터를 true로 하여 생성한 인스턴스에 대해 공정성을 유지하고 싶다면 tryLock() 사용 시 타임아웃 값을 인자로 받는 메서드를 통해 사용하여야한다!

 

예시에 사용한 코드는 깃헙에서 확인할 수 있다!

저작자표시 (새창열림)

'언어 > JAVA' 카테고리의 다른 글

JVM의 기본적인 내용 정리해보기!  (1) 2024.04.03
자바의 synchronized 키워드 동기화 범위 정리하기!  (1) 2024.03.23
동시성을 검증하는 테스트 코드 작성하기!  (1) 2024.02.25
[JAVA] Optional의 orElse()와 orElseGet()의 차이점  (1) 2022.08.26
  1. 공정한 락과 비공정한 락
  2. 잠금 방식
  3. 1. lock()
  4. 2. tryLock()
  5. tryLock() 사용 시 주의점
'언어/JAVA' 카테고리의 다른 글
  • JVM의 기본적인 내용 정리해보기!
  • 자바의 synchronized 키워드 동기화 범위 정리하기!
  • 동시성을 검증하는 테스트 코드 작성하기!
  • [JAVA] Optional의 orElse()와 orElseGet()의 차이점
SeongOnion
SeongOnion
서버는 꺼지지 않아요
SeongOnion
조무래기 코딩
SeongOnion
전체
오늘
어제
  • 분류 전체보기 (166)
    • 알고리즘 (81)
      • 이론 (8)
      • 문제풀이 (73)
    • 언어 (15)
      • Python (9)
      • JavaScript (1)
      • JAVA (5)
    • 데이터베이스 (5)
    • 프레임워크 (15)
      • Django (7)
      • Spring (8)
    • 그 외 공부 (37)
      • 운영체제 (1)
      • 자료구조 (14)
      • 네트워크 (5)
      • CS (2)
      • 기타 (6)
      • 트러블 슈팅 (9)
    • 프로젝트 (0)
    • 개발자취 (8)
    • 회고 (3)
    • 주저리주저리 (1)
    • 기타 (비개발) (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 큐
  • 회고
  • Django
  • 트러블 슈팅
  • DRF
  • 소수
  • 개발자
  • spring
  • 자바
  • 브루트포스
  • 장고
  • 투 포인터 알고리즘
  • 그리디알고리즘
  • 정렬 알고리즘
  • 웹
  • BFS/DFS
  • 이진탐색
  • 컨트리뷰트
  • 에라토스테네스의 체
  • 알고리즘
  • BFS
  • 코딩
  • 데이터베이스
  • 오픈소스
  • 파이썬
  • DP
  • 스택
  • 코딩테스트
  • 백준
  • 프로그래머스

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.2
SeongOnion
자바의 ReentrantLock 가볍게 알아보기!
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.