동기화

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가지가 있다.

  1. LockSupport.unpart(thread)
  2. 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