⬛ LockSupport
🟢 synchronized 단점
무한 대기: BLOCKED상태는 락이 풀릴 때까지 무한대기
공정성: 락획득 랜덤.특정 스레드가 너무 오랜기간 락획득 못할수도 있다.
🟢 LockSupport 기능
- park(): 스레드를 WAITING 상태로 변경
- unpark(): 스레드를 WAITING → RUNNABLE 상태로 변경
public class LockSupportMainV1 {
public static void main(String[] args) {
Thread thread1 = new Thread(new ParkTask(),"Thread-1");
thread1.start();
//잠시 대기하여 Thread-1이 park상태에 빠질 시간을 준다.
sleep(100);
log("Thread-1 state: " + thread1.getState());
log("main -> unpark(Thread-1)");
LockSupport.unpark(thread1); //1. unpark 사용
//thread1.interrupt(); //2. interrupt() 사용
}
static class ParkTask implements Runnable {
@Override
public void run() {
log("park 시작");
LockSupport.park();
log("park 종료, state: " + Thread.currentThread().getState());
log("인터럽트 상태: " + Thread.currentThread().isInterrupted());
}
}
}
인터럽트도 WAITING → RUNNABLE로 스레드를 깨운다.인터럽트를 사용해 스레드를 깨울수도있다. 이때 park()와 다른점은, 인터럽트 상태 로그에 true가 찍힌다.
⬛ LockSupport - 시간 대기
parkNanos(nanos): 스레드를 나노초 동안만 TIMED_WAITING → 이후 RUNNABLE
public class LockSupportMainV2 {
public static void main(String[] args) {
Thread thread1 = new Thread(new ParkTask(), "Thread-1");
thread1.start();
//잠시 대기하여 thread1이 park 상태에 빠질 시간을 준다.
sleep(100);
log("Thread-1 state: " + thread1.getState());
}
static class ParkTask implements Runnable {
@Override
public void run() {
log("park 시작, 2초 대기");
LockSupport.parkNanos(2000_000000); // parkNanos 사용
log("park 종료, state: " + Thread.currentThread().getState());
log("인터럽트 상태: " + Thread.currentThread().isInterrupted());
}
}
}
⬛ BLOCKED vs WAITING
공통점:
모두 스레드가 대기하며, 실행스케줄에 들어가지않는다.
차이점:
인터럽트가 걸릴때 대기 상태를 빠져나올수 있으면 WAITING(+TIMED_WAITING),
인터럽트가 걸려도 대기상태를 빠져나오지 못하면 BLOCKED이다.
BLOCKED는 자바 synchronized에서 락획득 대기할때만 사용하고, WAITING은 좀더 다양한 상황에서 사용된다.
⬛ ReentrantLock 이론
RetransLock은 위의 LockSupport를 활용해서 고수준기능을 제공한다. synchronized단점을 극복하고 스레드가 공정하게 락을 얻을 수 있도록 한다.
이때, 모니터 락과 BLOCKED상태는 synchronized에서만 사용됨에 주의하자. 여기서 사용하는 락은 객체 내부 모니터락이 아니다.
ReentrantLock내부에는 락과 락을 얻지 못해 대기하는 스레드를 관리하는 대기큐가 존재한다.락을 얻지못한 스레드는 LockSupport.park()가 내부에서 호출되고, 대기 큐에서 WAITING상태로 락을 대기한다.
🔸Lock 인터페이스
public interface Lock { //대표적 구현체;ReentrantLock
void lock(); //락획득&락무한대기(WAITING),인터럽트응답X
void lockInterruptibly() throws InterruptedException; //인터럽트발생시 락획득포기
boolean tryLock(); //락획득 즉시 성공여부반환
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock(); //락해제,락획득한스레드가 호출해야함
Condition newCondition(); //Condition객체생성하여반환,스레드가 특정조건&신호받을 수 있게함
}
🔸ReentrantLock 사용예시
- 비공정 모드(Non-fair mode): 무작위 락 획득 / 성능 우선,선점 가능, 기아 현상 가능성
- 공정 모드(Fair mode): 먼저 대기한 스레드가 먼저 락 획득 / 성능 저하,공정성보장,기아 현상 방지
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockEx {
//비공정 모드 락
private final Lock nonFairLock = new ReentrantLock();
//공정 모드 락
private final Lock fairLock = new ReentrantLock(true);
public void nonFairLockTest() {
nonFairLock.lock();
try {
//임계 영역
} finally {
nonFairLock.unlock();
}
}
public void fairLockTest() {
fairLock.lock();
try {
//임계 영역
} finally {
fairLock.unlock();
}
}
}
⬛ ReentrantLock 활용
주의할점은, 임계영역이 끝나면 반드시!!!락을 반납해야 한다는 것이다.그렇지않으면 대기스레드가 락을 얻지못한다.
lock.unlock()은 반드시 finally 블럭에 작성해야 한다. (그래야 예외가 발생해도 unlock()이 반드시 호출된다.)
@Override
public boolean withdraw(int amount) {
log("거래 시작: " + getClass().getSimpleName());
lock.lock(); // ReentrantLock 이용하여 lock을 걸기
try {
log("[검증 시작] 출금액: " + amount + ", 잔액: " + balance);
if (balance < amount) {
log("[검증 실패] 출금액: " + amount + ", 잔액: " + balance);
return false;
}
log("[검증 완료] 출금액: " + amount + ", 잔액: " + balance);
sleep(1000);
balance = balance - amount;
log("[출금 완료] 출금액: " + amount + ", 변경 잔액: " + balance);
} finally {
lock.unlock(); // ReentrantLock 이용하여 lock 해제
}
log("거래 종료");
return true;
}
⬛ ReentrantLock - 대기 중단
ReentrantLock을 사용하면 락을 무한대기 하지않고 중간에 빠져나올 수 있다. ; 내부적으로 tryLock()
또한 락획득실패 대기없이,락을 획득시도만 할 수도있다. ; 내부적으로 tryLock(long time, TimeUnit unit)
@Override
public boolean withdraw(int amount) {
log("거래 시작: " + getClass().getSimpleName());
//if (!lock.tryLock(500, TimeUnit.MILLISECONDS)) {
if (!lock.tryLock()) { //락을 획득할 수 없으면 바로포기,대기X
log("[진입 실패] 이미 처리중인 작업이 있습니다.");
return false;
}
try {
log("[검증 시작] 출금액: " + amount + ", 잔액: " + balance);
if (balance < amount) {
log("[검증 실패] 출금액: " + amount + ", 잔액: " + balance);
return false;
}
sleep(1000);
balance = balance - amount;
log("[출금 완료] 출금액: " + amount + ", 변경 잔액: " + balance);
} finally {
lock.unlock(); // ReentrantLock 이용하여 lock 해제
}
log("거래 종료");
return true;
}
'Java' 카테고리의 다른 글
[Java] 김영한의 실전 자바 - 고급 1편 섹션11 동시성 컬렉션 (0) | 2024.09.23 |
---|---|
[Java] 김영한의 실전 자바 - 고급 1편 섹션8 생산자 소비자 문제1 (0) | 2024.09.20 |
[Java] 김영한의 실전 자바 - 고급 1편 섹션6 동기화 - synchronized (0) | 2024.08.19 |
[Java] 김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성 섹션2,3 스레드 제어와 생명주기 (0) | 2024.07.28 |
[Java의 정석] chapter13 쓰레드 thread 알아보기 (0) | 2024.07.18 |