2013-04-01 2 views
0

여러 스레드가 같은 리소스에 동시에 액세스하지 못하게해야합니다. 나는 이러한 리소스를 가지고 있으므로 스레드가 불필요하게 서로를 블로킹하지 않도록 각 리소스 (하나의 전역 잠금보다는)에 대해 별도의 잠금 객체를 갖고 싶습니다.제거와 함께 ID 동기화하기

에디는 ConcurrentMap.putIfAbsent()https://stackoverflow.com/a/659939/82156으로 사용하여 훌륭한 해결책을 제시합니다.

// From https://stackoverflow.com/a/659939/82156 
public Page getPage(Integer id) { 
    Page p = cache.get(id); 
    if (p == null) { 
    synchronized (getCacheSyncObject(id)) { 
     p = getFromDataBase(id); 
     cache.store(p); 
    } 
    } 
} 

private ConcurrentMap<Integer, Integer> locks = new ConcurrentHashMap<Integer, Integer>(); 

private Object getCacheSyncObject(final Integer id) { 
    locks.putIfAbsent(id, id); 
    return locks.get(id); 
} 

그러나이 구현의 한 가지 문제점은 해시 맵이 무제한으로 확장된다는 것입니다. 스레드가 동시성을 망치지 않고 완료되면 해시 키를 제거하는 방법이 있습니까?

+0

'locks.remove (id, id)'의 문제점은 무엇입니까? – OldCurmudgeon

+0

잠금 맵은 실제 캐시보다 작습니다. 캐시를 정리할 수있는 메커니즘이 있습니까? 그렇다면 잠금 맵도 정리해야합니다. – flup

답변

1

Pyrce는 모든 리소스가 자체 잠금 객체를 가질 필요는 없다는 것을 깨닫게했습니다. 오히려 자원간에 공유 될 수있는 100 개의 잠금 오브젝트 풀을 사용할 수 있습니다. 그렇게하면 잠금 객체 세트가 무제한으로 늘어나지는 않지만 단일 글로벌 잠금을 제거하여 얻을 수있는 동시성 이점 대부분을 얻을 수 있습니다.

이것은 더 이상 ConcurrentHashMap을 사용할 필요가 없으며 열심히 초기화 된 간단한 배열을 사용할 수 있음을 의미합니다.

// getPage() remains unchanged 
public Page getPage(Integer id) { 
    Page p = cache.get(id); 
    if (p == null) { 
    synchronized (getCacheSyncObject(id)) { 
     p = getFromDataBase(id); 
     cache.store(p); 
    } 
    } 
} 

private int MAX_LOCKS = 100; 
private Object[] locks = new Object[MAX_LOCKS]; 

{ 
    for(int i=0; i<locks.length; ++i) 
     locks[i] = new Object(); 
} 


private Object getCacheSyncObject(final Integer id) { 
    return locks[ id % MAX_LOCKS ]; 
} 
1

특정 스레드가 특정 잠금을 요청하는 마지막 스레드라는 것을 알고 있다면 locks.remove(id, id) 만 사용할 수 있습니다. 그렇지 않으면 쉽게 동기화 할 수없는 두 가지 이벤트에 대한 원자 적 업데이트가 필요합니다.

잠금을 해제 한 다음 제거하면 다른 스레드가 해제 한 잠금을 가져올 수 있고 추가 스레드는 원래 잠금 개체를 알지 못하는 새 잠금 개체를 만듭니다. 이 경우 getFromDataBase(id) 또는 cache.store(p)을 동시에 호출하는 두 개의 스레드로 끝날 수 있습니다. 만약 당신이 대신에 새로운 쓰레드가 이미 새로운 자물쇠를 만들었을 때 당신이 이전의 자물쇠를 놓기를 기다리고있는 다른 쓰레드를 가지고 있었을 수 있습니다. 동일한 충돌이 발생할 수 있습니다.

기본적으로 시스템에 새 잠금을 추가하지 않고 원자 적으로 잠금을 해제하고 HashMap에서 제거 할 수 없습니다. 이 경우에는 해시 특정 잠금의 속도를 저하시키는 전역 잠금으로 끝나거나 동일한 제거 문제가 발생할 각 페이지마다 추가 잠금이 필요합니다. 또는 HashMap의 내부에 액세스 할 수 있다면 상위 수준의 논리에 약간의 버킷 잠금 장치를 사용할 수 있습니다. 그러나 그런 일을 구현하지 않는 것이 좋습니다.

해시 맵의 크기를 제한하는 한 가지 해결책은 대신 고정 크기 매핑을 사용하는 것입니다. Integer id를 큰 숫자 (10,000)로 변경하고 수정 된 숫자를 고유 한 잠금 ID로 사용하십시오. 두 개의 해시가 충돌하고 서로 다른 페이지에 대해 동일한 잠금을 요구하는 확률은 매우 작을 것이고 잠금에 의해 소비되는 메모리에 하드 캡을 가질 것입니다. 이 경우 Integer 잠금 객체를 정적 const 배열에 미리 할당하고 id 객체에서 직접 mod 해시를 요청할 수 있으므로 실제로 HashMap이 필요하지 않습니다.

또한 캐시 자체가 동시 읽기 쓰기로부터 보호되지 않는 한 캐시 외부에서 읽기를 수행하는 동안주의를 기울여야합니다. 다른 스레드가 질문에 지정된 코드를 읽는 동안 쓰레드가 쓰여질 수 있기 때문입니다.

+0

id를 고정 크기 정수 목록으로 수정 제안.때때로 두 개의 서로 다른 리소스를 동일한 잠금 장치에 적용 할 수도 있지만 이는 내 용도에 적합합니다. – emmby

관련 문제