희디비
[Java] LockSupprot, ReentrantLock 본문
LockSupprot
자바 1.5 부터 synchronized 단점을 해결 하기 위해 java.util.concurrent 패키지가 추가 되었습니다.
ReentrantLock를 알기 위해선 LockSupport에 대한 정보가 필요한데
LockSupport는 자신을 Waiting 상태로 바꾸거나 인터럽트 없이 다른 스레드를 깨울 수 있는 기능을 제공 합니다.
간단한 예제를 통해서 알아 보겠습니다.
- main 스레드 작업 : unpark를 통해 thread -1 번을 깨웁니다.
- thread -1 작업 : park를 통해 자신 스레드를 waiting 상태로 바꿉니다.
public static void main(String[] args) {
Thread thread1 = new Thread(new ParkTest(), "Thread-1");
thread1.start();
sleep(100);
log("Thread-1 state : " + thread1.getState());
log("main -> unpark(Thread-1)");
LockSupport.unpark(thread1);
}
static class ParkTest implements Runnable{
@Override
public void run() {
log("park 시작");
LockSupport.park();
log("park 종료, state : " + Thread.currentThread().getState());
log("인터럽트 상태 : " + Thread.currentThread().isInterrupted());
}
}
그림으로 표현한다면 과정은 아래와 같습니다.
Thread.join(millis) 와 같이 일정 시간 까지 Waiting 상태로 유지 하는 기능도 제공 합니다.
LockSupport.parkNanos(2000_000000);
LockSupport를 통해 자신을 Waiting 상태로 만들거나 다른 스레드를 깨울 수 있었습니다.
하지만 synchronized 처럼 동작 하기 위해선 락을 얻고 락을 얻지 못한 스레드는 대기할 자료구조가 있어야 하고
임계영역을 종료 한후 락을 반납 하여 대기 스레드중 하나를 깨워야 합니다.
자바에서는 저수준인 LockSupport를 활용 하여 실제 락 기능을 구현한 ReentrantLock 구현체를 제공 합니다.
ReentrantLock에 대해서 알아 보겠습니다.
ReentrantLock
LockSupprt 패키지를 활용 하여 락 기능이 동작 하는 구현체로 자바 1.5 Concurrent 패키지에 포함 되어 있습니다.
ReentrantLock은 락을 얻는 다양한 방법을 제공 하는데 예시를 통해 알아 보겠습니다.
마트에 물건 수량이 10개 존재 하고 두명이 동시에 물건을 각각 5개 6개 구매 하는 것입니다.
- lock : 락을 얻을때 까지 대기 ( 인터럽트 불가 )
- lockInterruptibly : 락 기능과 같습니다. ( 인터럽트 가능 )
private final Lock lock = new ReentrantLock();
private int count;
@Override
public boolean selling(int buyCount) {
try{
lock.lock();
log("[판매 시작] " + getClass().getSimpleName());
log("[검증 시작] 구매 개수 : " + buyCount + " 남은 물건 수 " + count);
if(count < buyCount){
log("[검증 실패] 마켓의 물건 개수가 부족 합니다.");
return false;
}
log("[검증 통과] 구매 개수 : " + buyCount + " 남은 물건 수 " + count);
sleep(1000);
count -= buyCount;
log("[판매 완료] 구매 개수 : " + buyCount + " 남은 물건 수 " + count);
} finally {
lock.unlock();
}
return true;
}
public MarketV2(int count) {
this.count = count;
}
@Override
public int getSelling() {
try{
lock.lock();
return count;
} finally {
lock.unlock();
}
}
실제 주문 메인코드는 아래와 같습니다.
public static void main(String[] args) throws InterruptedException {
Market market = new MarketV2(10);
Thread t1 = new Thread(() -> market.selling(5), "buyer-1");
Thread t2 = new Thread(() -> market.selling(6), "buyer-2");
t1.start();
t2.start();
log("t1.state() : " + t1.getState());
log("t2.state() : " + t2.getState());
t1.join();
t2.join();
log("[최종] 남은 물건 개수 : " + market.getSelling());
}
락 기능이 정상적으로 동작 하여 동시성 문제가 없음을 확인 할 수 있습니다.
과정이 어떻게 이루어 지는지 그림을 통해 설명 해보겠습니다. 두명의 구매자는 동시에 마켓에 들어 가려고 합니다.
구매자1번 스레드가 조금 더 빨리 실행 되어 락을 획득 하였습니다.
구매자 1번 스레드는 락을 얻어 마켓의 임계영역에 들어가 주문을 합니다.
구매자 2번 스레드는 락을 얻을려고 했지만 락이 없어서 waiting 상태로 대기 합니다.
구매자 1번 스레드는 주문을 성공적으로 마치고 락을 반납 했습니다. ( lock.unlock() )
구매자 2번 스레드는 락을 받고 임계영역에 들어가 주문을 하게 됩니다.
하지만 남은 수량이 5개 이므로 6개를 주문한 구매자 2번은 물건 개수가 부족 하다는 안내를 받습니다.
만약 락이 없으면 대기 하지 않고 바로 포기 하고 싶다면 어떻게 할까요?
락 획득을 시도 한 후 없다면 바로 포기하는 기능도 제공 합니다.
- boolean tryLock : 락을 획득 합니다. 만약 락이 없을 시 false를 반환 합니다.
- boolean tryLock(millis) : 일정 시간 까지만 락 획득을 위해 대기 합니다. 실패 시 false를 반환합니다.
if(lock.tryLock()){
try{
log("[판매 시작] " + getClass().getSimpleName());
log("[검증 시작] 구매 개수 : " + buyCount + " 남은 물건 수 " + count);
if(count < buyCount){
log("[검증 실패] 마켓의 물건 개수가 부족 합니다.");
return false;
}
log("[검증 통과] 구매 개수 : " + buyCount + " 남은 물건 수 " + count);
sleep(1000);
count -= buyCount;
log("[판매 완료] 구매 개수 : " + buyCount + " 남은 물건 수 " + count);
} finally {
lock.unlock();
}
return true;
} else{
log("[락 획득 실패]");
return false;
}
1번 구매자는 락을 얻어 물건 구매를 하였지만 2번 구매자는 락이 없어서 바로 포기 하였습니다.
ReentrantLock을 통해 락을 얻는 다양한 방법에 대해 알아 보았습니다.
synchronized 와 ReentrantLock의 차이점은 뭘까요?
- synchronized 는 락을 얻을 때 까지 Blocked 상태로 대기 하여 문제 발생 시 인터럽트를 할 수 없었습니다.
- ReentrantLock은 대기 시 Waiting 상태로 대기 하기 때문에 문제 발생 시
- 인터럽트를 발생 혹은 깨워서 종료 시킬 수 있고 락을 얻는 다양한 방법을 제공 합니다.
ReentrantLock은 Condition이라는 스레드 대기 집합을 가지고 있습니다.
다음 포스팅에선 소비자 생산자 문제에 대해 알아 보고 synchronized와 ReentrantLock 으로 해결 하는 과정을
알아 보겠습니다. 감사합니다!
+ 참고 lock 인터페이스에 사용법이 주석으로 있습니다!
위 글을 김영한 선생님 실전 자바 고급 1편을 보고 요약 한 것입니다.
자세한 내용이 궁금 하시다면 강의를 추천 드려요!
'Java' 카테고리의 다른 글
[Java] enum 왜 쓰는 걸까? (0) | 2024.11.27 |
---|---|
[Java] 생산자 소비자 문제 (0) | 2024.11.25 |
[Java] 메모리 가시성, 임계 영역 (4) | 2024.11.15 |
[Java] Join, Interrupt, Yield (0) | 2024.11.12 |
[Java] 스레드의 생성과 생명주기 (0) | 2024.11.10 |