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 인터페이스의 세 가지 구현이 포함되어 있다:
ReentrantLockReentrantReadWriteLock.ReadLockReentrantReadWriteLock.WriteLock
첫 번째 구현 ReentrantLock은 일반적으로 사용되는 경우이고, 다른 두 개의 구현은 ReentrantReadWriteLock 클래스의 내부 클래스이다. ReentrantReadWriteLock 클래스에는 ReadLock과 WriteLock 등 두 개의 락이 포함되어 있다.
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" 카테고리의 다른 글
- 리스너 리스트를 위한 WEAKHASHMAP 사용하기 (댓글 1개 / 트랙백 0개) 2006/03/08
- J2SE 5.0의 Java 2D API 기능 강화 (댓글 2개 / 트랙백 0개) 2006/05/12
- 3D 화면(scene)에 빛 효과 주기 (댓글 1개 / 트랙백 0개) 2004/07/30
- 다이얼로그 Modality (댓글 1개 / 트랙백 0개) 2006/06/09
- 쿠키 처리 (댓글 22개 / 트랙백 3개) 2007/07/23
- 사용자 데이터그램 프로토콜의 프로그래밍 (댓글 1개 / 트랙백 0개) 2004/06/30
- 락(LOCKS) (댓글 1개 / 트랙백 0개) 2005/09/22
- Java Web Start 퍼시스턴스 (댓글 3개 / 트랙백 0개) 2006/12/24
- AFFINETRANSFORM 이해하기 (댓글 3개 / 트랙백 0개) 2003/09/09
- 사용자 인터페이스에서 Action 사용하기 (댓글 5개 / 트랙백 2개) 2007/02/22
댓글을 달아 주세요
좋은 정보 감사해요~
2007/09/19 05:04