동기화
2024. 10. 5. 23:17ㆍ개발/Java
LockSupport
Synchronized는 한계가 있다.
- 무한 대기
- 공정성
따라서 Java 1.5부터 concurrent 패키지가 추가되었고 그 중에 LockSupport에 대해 알아보자
LockSupport
- park() : 스레드를 WAITING 상태로 변경한다.
- parkNanos(nanos) : 스레드를 나노초 동안만 TIMED_WAITING 상태로 변경한다.
- unpark(thread) : WAITING 상태의 대상 스레드를 RUNNABLE 상태로 변경한다.
WAITING 또는 TIMED_WAITING 상태를 RUNNABLE로 변경하는 방법은 2가지가 있다.
- LockSupport.unpart(thread)
- thread.interrupt()
public class LockSupportMain {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new ParkTest());
thread.start();
Thread.sleep(1000);
log("Thread-1 state: " + thread.getState());
LockSupport.unpark(thread);
OR
thread.interrupt();
Thread.sleep(1000);
Thread.sleep(1000);
log("Thread-1 state: " + thread.getState());
}
static class ParkTest implements Runnable {
@Override
public void run() {
log("Thread state: " + Thread.currentThread().getState());
LockSupport.park();
log("Thread state: " + Thread.currentThread().getState());
}
}
}
LockSupport.unpark(thread) 또는 thread.interrupt()를 사용하였을 때 결과는 다음과 같다.
2024-10-05 22:07:49.845 [ Thread-0]: Thread state: RUNNABLE
2024-10-05 22:07:50.840 [ main]: Thread-1 state: WAITING
2024-10-05 22:07:50.841 [ Thread-0]: Thread state: RUNNABLE
2024-10-05 22:07:52.847 [ main]: Thread-1 state: TERMINATED
다만 thread.interrupt()를 사용했을 때 Thread.currentThread().isInterrupted() 는 true이다.
LockSupport.parkNanos 를 대신 사용하게 되면
static class ParkTest implements Runnable {
@Override
public void run() {
log("Thread state: " + Thread.currentThread().getState());
LockSupport.parkNanos(2000_000000);
log("Thread state: " + Thread.currentThread().getState());
}
}
2024-10-05 22:16:01.770 [ Thread-0]: Thread state: RUNNABLE
2024-10-05 22:16:02.764 [ main]: Thread-1 state: TIMED_WAITING
2024-10-05 22:16:02.766 [ Thread-0]: Thread state: RUNNABLE
2024-10-05 22:16:04.771 [ main]: Thread-1 state: TERMINATED
상태만 TIMED_WAITING으로 바뀌고 LockSupport.unpart(thread) 또는 thread.interrupt()에 의해 RUNNABLE로 바뀌게 된다.
ReentrantLock - Lock Interface의 구현체
- 내부적으로 LockSupport를 사용한다.
- 그리고 모니터락을 이요하는 것이 아닌 내부적인 대기 큐와 lock 상태 필드 같은 것이 있다.
void lock()
- 락을 획득하고 만약 다른 스레드가 이미 락을 획득했다면 락이 풀릴 때까지 현재 스레드는 대기(WAITING)한다. 이 메서드는 인터럽트에 응답하지 않는다.
- interrupt()가 걸렸을 때 → WAITING → RUNNABLE은 되지만 다시 WAITING 상태가 된다.
void lockInterruptibliy()
- 락을 획득하고 만약 다른 스레드가 이미 락을 획득했다면 락이 풀릴 때까지 현재 스레드는 대기(WAITING)한다. 대기 중에 인터럽트가 발생하면 InterruptedException이 발생하며 락 획득을 포기한다.
boolean tryLock()
- 락 획득을 시도하고, 즉시 성공 여부를 반환한다. 만약 다른 스레드가 이미 락을 획득했다면 false를 반환하고, 그렇지 않다면 락을 획득하고 true를 반환한다.
boolean tryLock(long time, TimeUnit unit)
- 주어진 시간 동안 락 획득을 시도한다. 주어진 시간 안에 락을 획득하면 true를 반환한다. 주어진 시간이 지나도 락을 획득하지 못한 경우 false를 반환한다. 이 메서드는 대기 중 인터럽트가 발생하면 InterruptedException이 발생하며 락 획득을 포기한다.
void unlock()
- 락을 해제한다. 락을 해제하면 락 획득을 대기 중인 스레드 중 하나가 락을 획득할 수 있게 된다.
- 락을 획득한 스레드가 호출해야 하며, 그렇지 않으면 IllegalMonitorStateException이 발생할 수 있다.
Condition newCondition()
- Condition 객체를 생성하여 반환한다. Condition 객체는 락과 결합되어 사용되며, 스레드가 특정 조건을 기다리거나 신호를 받을 수 있도록 한다.
ReentrantLock은 공정성(fairness) 모드와 비공정(non-fair) 모드로 설정할 수 있다.
공정 모드
- 락을 먼저 요청한 스레드가 락을 먼저 획득(대기 큐에서 먼저 대기한 스레드가 락을 먼저 획득)
- 모든 스레드가 언젠가 락을 획득한다.
- 락을 획득하는 속도가 느려진다.
비공정 모드
- 락을 획득하는 속도가 빠르다.
- 새로운 스레드가 기존 대기 스레드보다 먼저 락을 획득할 수 있다.
- 특정 스레드가 계속해서 락을 획득하지 못할 수 있다.
'개발 > Java' 카테고리의 다른 글
Mutex와 Semaphore (0) | 2024.10.20 |
---|---|
메모리 가시성 (0) | 2024.10.20 |
스레드 제어와 생명 주기 (0) | 2024.10.02 |
JVM 메모리 영역 (0) | 2024.09.29 |
멀티스레드와 동시성 (0) | 2024.09.24 |