Semaphore
Semaphore (카운팅 세마포어)
일정 개수의 허가(permit)를 유지하는 세마포어예요. acquire()는 허가를 얻을 수 있을 때까지 필요한 경우 블로킹하고, release()는 허가를 다시 추가해 블로킹된 취득자를 풀어줘요. 리소스에 접근할 수 있는 쓰레드 수를 제한하는 데 자주 쓰여요.
본문
개념적으로 Semaphore는 허가들의 집합을 유지해요. 실제 허가 객체는 사용되지 않고, 단지 사용 가능한 수의 개수만 세면서 동작해요.
예를 들어 항목 풀에 대한 접근을 세마포어로 제어하는 클래스는 이렇게 생겼어요.
class Pool {
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
}
public void putItem(Object x) {
if (markAsUnused(x)) available.release();
}
protected Object[] items = ...; // 관리 대상 항목들
protected boolean[] used = new boolean[MAX_AVAILABLE];
protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) { used[i] = true; return items[i]; }
}
return null; // 도달하지 않음
}
protected synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) { used[i] = false; return true; }
else return false;
}
}
return false;
}
}
항목을 얻기 전 각 쓰레드는 세마포어에서 허가를 획득해 항목을 사용할 수 있음을 보장받아요.
허가가 최대 1개까지만 유지되도록 초기화한 세마포어는 상호 배제 락으로 동작할 수 있어요. 이를 이진 세마포어라고 부르며, 대부분의 Lock 구현과 달리 소유자가 아닌 다른 쓰레드도 해제할 수 있다는 특성이 있어요.
생성자는 선택적으로 공정성(fairness) 파라미터를 받아요.
false일 때는 쓰레드가 허가를 획득하는 순서를 보장하지 않아요. 밀어내기(barging)가 허용되죠.true일 때는acquire메서드들을 호출한 순서대로(FIFO) 허가를 받아요.- 시간 제한이 없는
tryAcquire는 공정성 설정을 따르지 않아요.
Semaphore s = new Semaphore(1, true);
if (s.tryAcquire()) {
try { /* 임계 영역 */ } finally { s.release(); }
}
리소스 접근을 제어하는 세마포어는 스타베이션을 막기 위해 공정하게 초기화하는 게 일반적이에요.
acquire(3)처럼 한 번에 여러 허가를 취득·해제하는 편의 메서드도 있어요. 다만 이들은 선호 순서를 보장하지 않아요.
메모리 일관성 효과: 어떤 쓰레드에서 release() 같은 "해제" 메서드를 부르기 전의 동작은, 다른 쓰레드에서 acquire() 같은 "취득" 메서드가 성공한 뒤의 동작에 happen-before해요.