락(LOCKS)

Java SE 2005/09/22 10:10 Posted by Sun

J2SE 5.0 라이브러리에서 가장 인기 있는 특징 중 하나는 병행(concurrency) 유틸리티가 추가되었다는 것이다. JSR 166의 일부로 제공되는 이 유틸리티는 개발자들에게 synchronized 키워드와 관련 동기화 블록을 능가하는 진보된 병행 프로그래밍 능력을 제공한다. 병행 유틸리티에 의해 향상된 분야의 하나로 로킹(locking)을 들 수 있다. 제공되는 기능들 중에서 병행 유틸리티는 락 타임아웃, 락 당 복수의 조건 변수, 읽기/쓰기 락, 그리고 락 대기중인 쓰레드를 인터럽트할 수 있는 옵션 등을 지원한다. 그 밖의 락 지원에 관한 자세한 내용은 java.util.concurrent.locks 패키지에 관한 문서를 참조하도록 한다.

패키지의 기본이 되는 것은 락을 획득/해제하는 일련의 메소드를 제공하는 Lock 인터페이스로서, 일반적인 사용 순서는 락을 획득하고, 보호된 리소스에 액세스하고, 락을 해제하는 것이다. 다음은 전형적인 사용 패턴의 한 예이다:

     Lock l = ...;
     l.lock();
     try {
         // protected resource access
     } finally {
         l.unlock();
     }

Lock 클래스가 이런 식으로 사용될 경우에는 전형적인 동기화 락과 유사하게 작동한다

    synchronized(lockVariable) {
        // protected resource access
    }

엄격히 말하면, 유사하지만 동일하지는 않다. finally 블록에서 unlock() 호출을 얻지 못하면 애플리케이션은 제대로 기능하지 않게 되는데, 동기화된 블록의 경우에는 반드시 그렇지는 않다.

락을 획득하는 Lock 인터페이스가 제공하는 또 하나의 메소드가 바로 lockInterruptibly()이다. 쓰레드는 락을 요청한 후 대기하게 되는데, lockInterruptibly() 메소드는 대기중인 쓰레드를 인터럽트할 수 있게 해준다. 한편, lock() 메소드는 경우가 다르다. lockInterruptibly()로 락 대기중인 쓰레드에서 interrupt()가 호출되면 대기 상태가 인터럽트된다. 그러면 대기중인 쓰레드가 “깨어나고” InterruptedException이 던져진다. 이 경우 보호된 리소스에 액세스하려는 그 어떠한 시도도 이루어지지 않으며 계속해서 다른 연산이 수행된다.

lockInterruptibly() 메소드에 대한 전형적인 사용 패턴은 다음과 같다:

    Lock l = new ReentrantLock();
    try {
        l.lockInterruptibly();
        try {
            // protected resource access
        } finally {
            l.unlock();
        }
    } catch (InterruptedException e) {
        System.err.println("Interrupted wait");
    } 

여기서, 내부의 try-finally는 리소스가 보유되는 상태에서 예외를 처리하고, 외부의 try-catch는 리소스를 획득하면서 예외를 처리한다. 락 획득이 실패할 경우에는 절대로 unlock을 호출해서는 안 된다.

주의사항: lockInterruptibly()를 호출한다고 해서 반드시 대기 상태를 인터럽트할 수 있는 것은 아니다. 또한, 모든 Lock 구현이 이 연산을 지원하지는 않는다.

Lock 인터페이스가 제공하는 또 하나의 메소드인 tryLock()은 타임아웃을 작동시키는데, tryLock() 메소드에는 두 가지 버전이 있다. 첫째 버전의 경우에는 아규먼트를 받지 않는다. 이 버전은 락을 얻으려 시도하고, 락이 가용 상태가 아닐 경우 즉시 실패하게 된다.

    Lock lock = ...;
    if (lock.tryLock()) {
      try {
        // protected resource access
      } finally {
        lock.unlock();
      }
    } else {
        // alternative operation
    }

이는 마치 여러분이 복사실로 갔을 때 누군가가 오래 걸리는 복사 작업을 하고 있으면 속도는 느리지만 이용 가능한 복사기를 찾는 경우와 비슷하다.

tryLock 메소드의 둘째 버전은 타임아웃을 표시하는 두 개의 파라미터를 받는다. 첫 번째 아규먼트는 최대 대기 시간을 지정하기 위한 긴 변수이고, 두 번째 아규먼트는 시간 단위 지정을 위한 TimeUnit이다.

예를 들어, lock.tryLock(300, TimeUnit.MILLISECONDS)은 락 획득을 시도하는데, 300밀리초를 대기한 후에도 락을 얻을 수 없으면 false를 반환하여 락을 얻을 수 없음을 표시한다. 이와 대조적으로, lock.tryLock(30, TimeUnit.SECONDS)은 최대 30초를 대기하며, 이때 값과 단위를 모두 명확하게 지정해야 한다.

TimeUnit 클래스는 단위를 초, 밀리초, 마이크로초, 나노초로 지정할 수 있게 해준다.

1 second = 1 thousand milliseconds
1 second = 1 million microseconds
1 second = 1 billion nanoseconds

지금까지 Lock 인터페이스 이용 방법을 알아보았지만 정작 Lock을 생성하는 방법에 대해서는 배울 기회가 없었다. 앞의 예제들은 모두 다음과 같은 식으로 표시된다:

   Lock lock = ...;

java.util.concurrent.locks 패지키에는 Lock 인터페이스의 세 가지 구현이 포함되어 있다:

  • ReentrantLock
  • ReentrantReadWriteLock.ReadLock
  • ReentrantReadWriteLock.WriteLock

첫 번째 구현 ReentrantLock은 일반적으로 사용되는 경우이고, 다른 두 개의 구현은 ReentrantReadWriteLock 클래스의 내부 클래스이다. ReentrantReadWriteLock 클래스에는 ReadLockWriteLock 등 두 개의 락이 포함되어 있다.

ReentrantLock에 대한 전형적인 사용 패턴은 다음과 같다:

     Lock l = new ReentrantLock();
     l.lock();
     try {
         // protected resource access
     } finally {
         l.unlock();
     }

읽기가 길고 빈번하며 쓰기가 드문 경우에는 읽기/쓰기 락을 사용한다. 그러면 보호된 오브젝트에 대한 액세스는 여기에 표시된 것처럼 획득된 별도의 락을 사용하게 된다.

  ReadWriteLock rwl = new ReentrantReadWriteLock();
  Lock readLock = rwl.readLock();
  Lock writeLock = rwl.writeLock();

그런 다음 수행하고자 하는 액세스의 종류에 적합한 락을 이용하여 ReentractLock과 동일한 방식으로 락을 사용한다.

ReadWriteLock 인터페이스의 유일한 구현은 ReentrantReadWriteLock 클래스 그 자체이다.

Lock 지원에 관한 자세한 내용은 패키지 설명(package description)을 참조하도록 한다.

"Java SE" 카테고리의 다른 글

2005/09/22 10:10 2005/09/22 10:10

TRACKBACK :: http://blog.sdnkorea.com/blog/trackback/178

댓글을 달아 주세요

  1. 박정숙  수정/삭제  댓글쓰기

    좋은 정보 감사해요~

    2007/09/19 05:04
[로그인][오픈아이디란?]

◀ Prev 1  ... 487 488 489 490 491 492 493 494 495  ... 626  Next ▶