JAVA

[JAVA] 자바 쓰레드 동기화(2) - ReentrantLock과 Condition

jhkimmm 2022. 7. 6. 03:14
 

[JAVA] 자바 쓰레드 동기화(1) - synchronized, wait()/notify()

멀티 쓰레드 환경에서 Critical section 문제를 해결하기 위해서는 Critical section에 대해 쓰레드를 동기화 해야합니다. 자바에서는 아래 두 가지 방법으로 쓰레드를 동기화 할 수 있습니다. 1) synchronize

jhkimmm.tistory.com

 

저번 포스팅에 이어서 java.util.concurrent.locks 패키지의 lock 클래스 중 ReentrantLock 클래스를 이용하여 쓰레드를 동기화 하는 방법을 알아보겠습니다.

ReentrantLock이란

ReentrantLock은 가장 일반적인 lock이며, synchronized 블럭의 wait() & notify() 처럼 await() & signal()을 이용해 특정 조건에서 lock을 풀고 나중에 다시 lock을 얻어 이후의 작업을 수행할 수 있습니다.

 

synchronized 블럭은 자동으로 lock이 잠기고 풀리기 때문에 편리하지만, ReentrantLock 클래스를 이용하면 다양한 고급기능을 사용할 수 있습니다.

  • Lock polling을 지원한다.
  • 코드가 단일 블록 형태를 넘어서는 경우 사용 가능 하다.
  • 타임 아웃을 지정할 수 있다.
  • Condition을 적용해서 대기 중인 쓰레드를 선별적으로 깨울 수 있다.
  • lock 획득을 위해 waiting pool에 있는 쓰레드에게 인터럽트를 걸 수 있다.

즉, 위의 기능이 필요할 때 synchronized 블럭 대신 ReentrantLock을 적용하면 됩니다.

사용법

//Constructor
ReentrantLock()
ReentrantLock(boolean fair)

ReentrantLock은 두 개의 생성자를 가지고 있으며, fair를 true로 주면 가장 오래 기다린 쓰레드가 lock을 얻을 수 있도록 공정하게 처리합니다.

단, 공정하게 처리하려면 어떤 쓰레드가 가장 오래 기다렸는지 확인하는 과정이 필요하므로 성능은 떨어집니다.

//Method
void lock() // lock 잠금
void unlock() // lock 해제
boolean isLocked() // lock이 잠겼는지 확인
boolean tryLock() // lock polling
boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException

자동으로 lock의 잠금과 해제가 관리되는 synchronized 블럭과는 다르게,  수동으로(명시적으로) lock을 잠그고 해제 합니다.

 

일반적인 lock()은 lock을 얻을 때 까지 쓰레드를 블락시키므로 context swtich에 따른 overhead가 발생할 수 있는데, Critical Section의 수행시간이 매우 짧을 경우에는 tryLock()을 통한 Lock polling(SpinLock)을 통해 효율적인 locking이 가능합니다.

 

tryLock()에 시간을 설정하면, 지정된 시간 동안 Lock을 얻지 못한 경우 다시 작업을 시도할 것인지 포기할 것인지 결정할 수 있습니다.

//기본적인 사용법
class TestClass{
	private ReentrantLock lock = new ReentrantLock(); // Lock 생성
    
    public testMethod(){
    	lock.lock();
        try{
        	//Critical Section
        } finally {
        	lock.unlock();
        }
    }
}

Condition

synchronized의 wait() & notify()는 쓰레드의 종류를 구분하지 않고 공유 객체의 waiting pool에 같이 몰아 넣어 선별적인 통지가 불가능 했지만, 

ReentrantLock과 Condition을 사용하면 쓰레드의 종류에 따라 구분된 waiting pool에서 따로 기다리도록 하여 선별적인 통지를 가능하게 합니다.

private ReentrantLock lock = new ReentrantLock(); // lock 생성
// lock으로 Condition 생성
private Condition forTask1 = lock.newCondition();
private Condition forTask2 = lock.newCondition();

위와 같이 Condition을 생성하고 wait() & notify() 대신 Condition의 await() & signal()을 사용하면 됩니다.

Object Condition
void wait() void await()
void awaitUninterruptibly()
void wait(long timeout) boolean await(long time, TimeUnit unit)
long awaitNanos(long nanosTimeout)
boolean awaitUntil(Date deadline)
void notify() void signal()
void notifyAll() void signalAll()