자바 병렬 프로그래밍 summary - 13. 명시적인 락

프로그래밍/병렬프로그래밍 2010. 4. 19. 10:44


 개인적인 스터디를 위해 위 책의 내용을 정리한 내용입니다. 자세한 내용을 확인하고 싶으신 분은 위 책을 구매하셔서 확인하시기 바랍니다. http://www.yes24.com/24/goods/3015162?CategoryNumber=001001003


ReentrantLock 은 자바5.0 에서 추가됬으며 암묵적인 락으로 할 수 없는 고급 기능을 가지고 있다.

 

13.1 Lock ReentrantLock

- Lock 인터페이스는 조건없는 락, 폴링 락, 타임아웃이 있는 락, 락 확보 대기상태에 인터럽트를 걸 수 있는 기능을 가진다.

- 모든 작업이 명시적이다.

public interface Lock {

       void lock();

       void lockInterruptibly() throws InterruptedException();

       boolean tryLock();

       boolean tryLock( long timeout, TimeUnit unit() throws InterruptedException();

       void unlock();

       Condition newCondition();

}

 

ReentrantLock Lock 인터페이스를 구현한다. synchronized 구문 과 동일한 메모리 가시성과 상호 배제 기능을 제공한다.

Lock lock = new ReentrantLock();

       lock.lock();

       try{

            

       }finally{

             lock.unlock();

}

ReentrantLock finally 구문에서 반드시 락을 해제해 주어야 한다.

 

 

13.1.1 폴링과 시간 제한이 있는 락 확보 방법

락을 확보할 때 시간제한을 두거나 폴링 방법(trylock)을 사용하면 락을 확보하지 못하는 상황에도 통제권을 다시 얻을 수 있다.

public boolean trySendOnSharedLine(String message, long timeout, TimeUnit unit) throws InterruptedException{

             long nanoToLock = unit.toNanos(timeout) - estimateNanosToSend(message);

             if(!lock.tryLock(nanosToLock, NANOSECONDS))

                    return false;

             try{

                    return sendOnSharedLine(message);

             }finally{

                    lock.unlock();

             }

       }

 

13.1.2 인터럽트 걸 수 있는 락 확보 방법

public boolean sendOnSharedLine(String message) throws InteruptedException{

             lock.lockInterruptibly();

             try{

                    return cancellableSendOnSharedLine(message);

             }finally{

                    lock.unlock();

             }

       }

 

13.1.3 블록을 벗어나는 구조의 락

 

13.2 성능에 대한 고려

자바 5.0과 함께 ReentrantLock이 처음 소개됐을 때 암묵적인 락에 비해 훨씬 나은 경쟁 성능을 보여줬다. 하지만 자바 6.0에서는 암묵적인 락과 ReentrantLock 의 성능이 별반 차이가 없다.

 

13.3 공정성

ReentrantLock 은 두 종류의 락 공정성 설정을 지원한다. 불공정 unfair 방법 과 공정 fair 방법이다.

공정한 방법은 락을 확보하고자 하는 스레드는 항상 큐의 맨끝 대기열에 들어간다.

불공정 방법은 락을 확보하려는 시점에 락이 사용중이라면 대기열에 들어가게되고 확보하려는 찰나에 락이 해제되었다면 대기열에 대기중인 스레드를 뛰어넘어 락을 확보하게 된다.

 

공정하게만 처리하다 보면 스레드를 반드시 멈추고 다시 실행시키는 동안에 성능에 큰 지장을 줄 수 있기 때문이다.

 

13.4 synchronized 또는 ReentrantLock 선택

ReentrantLock 은 암묵적인 락만으로는 해결할 수 없는 복잡한 상황에서 사용하기 위한 고급 동기화 기능이다. 다음과 같은 고급 동기화 기법을 사용해야 하는 경우에만 ReentrantLock을 사용하도록 하자

- 락을 확보할 때 타임아웃을 지정해야 하는 경우

- 폴링의 형태로 락을 확복하고자 하는 경우

- 락을 확보하느라 대기 상태에 들어가 있을 때 인터럽트를 걸 수 있어야 하는 경우

- 대기 상태 큐 처리 방법을 공정하게 해야 하는 경우

- 코드가 단일 블록의 형태를 넘어서는 경우

그 외의 경우에는 synchronized 블록을 사용하도록 하자

 

자바 5.0에서는 synchronized 블록을 사용했을 때 스레드 덤프를 통해 어느 메소드에서 어느 락을 확보하고 있고, 데드락에 걸린 스레드가 있는지, 어디에서 데드락이 걸렸는지 확인할 수 있는 반면 ReentrantLock 을 알 수 없다.

자바 6.0에서는 ReentrantLock 에 모니터링 인터페이스를 추가하여 해결하였다.

 

13.5 읽기-쓰기 락

읽기 작업은 여러 개를 한꺼번에 처리할 수 있지만 쓰기 작업은 혼자만 동작할 수 있는 구조의 동기화를 처리해 주는 락이 읽기-쓰기 락 read-write lock 이다.

대다수의 작업은 데이터 변경이 아닌 읽기 작업이다. 이런 상황에서는 락의 조건을 풀어 읽기 연산은 여러 스레드에서 동시에 실행할 수 있도록 해주면 성능을 크게 향상시킬 수 있다.

 

public interface ReadWriteLock{

       Lock readLock();

       Lock writeLock();

}

 

자바 5.0에서 읽기 락은 읽기 작업을 하고 잇는 스레드의 개수만 세고 실제로 어느 스레드가 읽고 있는지는 상관없다. 반면 자바 6.0에서는 어느 스레드가 읽기 락을 확보했는지 추적하도록 되어 있다.

: