SW/Java

Java 동시성 프로그래밍에서 LockSupport 클래스의 활용 및 이해

얇은생각 2024. 9. 30. 07:30
반응형

Java에서 동시성 프로그래밍을 다루는 개발자들에게 있어 LockSupport 클래스는 매우 유용한 도구 중 하나입니다. 이 클래스는 lock이나 synchronized와 같은 전통적인 동기화 메커니즘과는 다르게, 비교적 저수준에서 스레드를 제어하는 데 사용됩니다. 이를 통해 효율적으로 스레드의 실행을 제어하고, 시스템 자원을 관리할 수 있습니다. 이번 글에서는 LockSupport가 어떻게 작동하는지, 어떤 경우에 사용되는지, 그리고 관련된 핵심 개념들을 설명하겠습니다.

 

Java 동시성 프로그래밍에서 LockSupport 클래스의 활용 및 이해

 

 

LockSupport 클래스란?

LockSupport는 Java의 java.util.concurrent.locks 패키지에서 제공하는 유틸리티 클래스로, 스레드를 제어하는 기본적인 메서드들을 제공합니다. 이 클래스는 스레드의 '정지'와 '재개'에 대한 저수준 제어를 가능하게 하며, 다른 동기화 메커니즘의 기초적인 빌딩 블록으로 활용됩니다.

 

 

주요 메서드

LockSupport 클래스는 주로 스레드의 실행을 일시 중지시키거나 재개하는 기능을 제공합니다. 이를 위해 다음과 같은 메서드를 사용할 수 있습니다.

  • park(): 현재 스레드를 정지시킵니다. 스레드가 중단 상태로 들어가며, 다른 스레드가 unpark()을 호출하기 전까지 재개되지 않습니다.
  • parkNanos(long nanos): 지정된 시간 동안 스레드를 정지시킵니다. nanos에 따라 스레드는 해당 시간 동안 정지됩니다.
  • unpark(Thread thread): thread 스레드를 재개시킵니다. park()으로 정지된 스레드에 다시 실행 기회를 부여하는 메서드입니다.
  • getBlocker(Thread t): 특정 스레드가 무엇에 의해 정지되었는지 확인할 수 있습니다.

 

 

LockSupport의 내부 동작: Permit 개념

LockSupport는 기본적으로 Permit이라는 개념을 사용합니다. 각 스레드는 LockSupport를 사용하여 하나의 permit을 가질 수 있으며, 이 permit이 있으면 park() 메서드를 호출할 때 스레드가 즉시 재개됩니다. 초기 permit 값은 0이지만, unpark() 메서드를 호출하면 permit이 1이 됩니다. 그 후, park() 메서드가 호출되면 permit이 소모되고 스레드는 계속 실행됩니다.

 

 

park() 메서드

park() 메서드는 스레드를 정지시키는 기능을 합니다. 만약 permit이 1로 설정되어 있다면, park() 메서드는 즉시 반환되고 스레드는 계속 실행됩니다. 하지만 permit이 0이라면, 해당 스레드는 다른 스레드가 unpark() 메서드를 호출할 때까지 대기 상태에 들어가게 됩니다.

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
        System.out.println("스레드가 실행 중입니다.");
        LockSupport.park();
        System.out.println("스레드가 다시 실행되었습니다.");
    });

    thread.start();
    
    Thread.sleep(2000); // 2초 후 스레드 재개
    System.out.println("스레드를 재개합니다.");
    LockSupport.unpark(thread); // 스레드 재개
}
 

 

위 예제에서 보듯, park() 메서드는 스레드를 일시 중지시키고, unpark() 메서드가 호출되면 스레드가 다시 실행됩니다.

 

 

unpark() 메서드

unpark() 메서드는 특정 스레드를 다시 실행할 수 있도록 permit을 1로 설정하는 역할을 합니다. park() 메서드가 호출된 스레드는 이 permit을 소모하여 다시 실행될 수 있습니다. 즉, unpark()은 스레드가 park()에 의해 정지된 상태에서 재개할 수 있게 해주는 중요한 메서드입니다.

 

 

LockSupport의 활용 예제

이제 LockSupport의 실제 사용 사례를 살펴보겠습니다. LockSupport는 다양한 시나리오에서 활용될 수 있지만, 주로 동시성 제어에서 유용하게 사용됩니다. 아래에서는 대표적인 예제를 통해 LockSupport가 어떻게 활용되는지 알아보겠습니다.

 

1. 스레드 간 동기화

LockSupport를 사용하여 스레드 간 동기화를 구현할 수 있습니다. 아래 코드는 하나의 스레드가 park() 메서드로 대기 상태에 들어가고, 다른 스레드가 unpark()을 호출하여 이를 재개시키는 예제입니다.

public class LockSupportExample {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("스레드가 실행 중입니다.");
            LockSupport.park();
            System.out.println("스레드가 다시 실행되었습니다.");
        });

        thread.start();

        Thread.sleep(1000); // 1초 동안 대기
        System.out.println("스레드를 재개합니다.");
        LockSupport.unpark(thread); // 스레드 재개
    }
}
 
 
 

2. FIFO Mutex 구현

LockSupport는 간단한 FIFO(First-In-First-Out) 뮤텍스를 구현하는 데에도 사용될 수 있습니다. LockSupport를 통해 스레드를 순차적으로 대기시키고, 차례대로 재개하는 방식으로 동기화 메커니즘을 구현할 수 있습니다.

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

public class FIFOMutex {

    private final AtomicBoolean locked = new AtomicBoolean(false);
    private final Queue<Thread> waiters = new ConcurrentLinkedQueue<>();

    public void lock() {
        boolean wasInterrupted = false;
        Thread current = Thread.currentThread();
        waiters.add(current);

        while (waiters.peek() != current || !locked.compareAndSet(false, true)) {
            LockSupport.park(this);
            if (Thread.interrupted()) {
                wasInterrupted = true;
            }
        }

        waiters.remove();
        if (wasInterrupted) {
            current.interrupt();
        }
    }

    public void unlock() {
        locked.set(false);
        LockSupport.unpark(waiters.peek());
    }
}
 

위 코드는 FIFO 방식으로 스레드를 대기시키고, 차례대로 재개하는 간단한 뮤텍스 구현입니다. 각 스레드는 대기열에 추가되며, unpark() 메서드를 통해 순차적으로 재개됩니다.

 

 

3. Rate Limiter 구현

LockSupport는 주기적인 작업을 처리하는 Rate Limiter와 같은 동시성 제어 구조에서도 유용하게 사용될 수 있습니다. 특정 시간 동안 제한된 자원을 사용할 수 있는 Rate Limiter에서는 스레드가 필요한 시간이 될 때까지 대기하도록 설정할 수 있습니다.

public class RateLimiterExample {

    public void waitForPermission(long nanosToWait) {
        long deadline = System.nanoTime() + nanosToWait;
        boolean wasInterrupted = false;

        while (System.nanoTime() < deadline && !wasInterrupted) {
            LockSupport.parkNanos(deadline - System.nanoTime());
            wasInterrupted = Thread.interrupted();
        }

        if (wasInterrupted) {
            Thread.currentThread().interrupt();
        }
    }
}
 
 
 

결론

LockSupport는 Java에서 동시성 프로그래밍을 효율적으로 처리하는 데 중요한 도구입니다. 이를 통해 스레드 간의 동기화를 쉽게 구현할 수 있으며, 저수준에서 스레드의 실행 흐름을 제어할 수 있습니다. park()와 unpark() 메서드를 적절히 활용하면 시스템 자원을 효율적으로 사용하면서도 복잡한 동기화 문제를 해결할 수 있습니다.

또한, LockSupport는 다른 동기화 메커니즘과의 조합으로 더욱 강력한 기능을 제공합니다. 예를 들어, FIFOMutex나 Rate Limiter와 같은 구조에서는 스레드를 정지시키고 재개하는 과정에서 중요한 역할을 합니다. 이를 통해 효율적인 동시성 제어를 구현할 수 있습니다.

반응형