2011-09-08 3 views
2

가능한 경우 읽을 때 잠그지 않으려합니다. 그러나 부분적으로 초기화 된 멤버가 포함되어 있지 않더라도 이중 확인 잠금과 같은 "느낌"이 있습니다.해시 맵의 객체에 대한이 지연 초기화 패턴이 스레드로부터 안전한가요?

이것은 좋은 구조입니까?

private final Map<String, Stuff> stash = new HashMap<String, Stuff>(); 

public Stuff getStuff(String name) { 

    if (stash.containsKey(name)) 
     return stash.get(name); 

    synchronized(stash) { 
     if (stash.containsKey(name)) { 
      return stash.get(name); 
     } 
     else { 
      Stuff stuff = StuffFactory.create(name); 
      stash.put(name, stuff); 
      return stuff; 
     } 
    } 
} 
+2

재미를위한 것이 아닌 경우 Guava의 [MapMaker] (http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/MapMaker.html)와 같은 것을 사용해야합니다. –

답변

6

아니요,이 구조체는 스레드로부터 안전하지 않습니다.

스레드 writer이 맵에 무엇인가 넣고 있으며 맵이 너무 작 으면 크기를 조정해야한다고 가정합니다. 이 작업은 synchronized 블록 내부에서 수행되므로 괜찮다고 생각할 수 있습니다.

크기 조정 중에는지도의 내용이 보장되지 않습니다.

이제는 스레드 reader이 기존 요소에 대해 getStuff을 호출한다고 가정합니다. 이 스레드는 containsKeyget의 첫 번째 호출에 대해 synchronized 블록을 방문하지 않으므로지도에 직접 액세스 할 수 있습니다. 그것은 정의되지 않은 상태의지도를 발견 할 것이고, 읽기만하더라도 내용이 정의되지 않은 데이터에 액세스합니다. 가능한 결과 중입니다

  • getStuff 반환 null가 안가.
  • getStuff은 의도 한 Stuff을 반환합니다.
  • getStuff은 크기 조정 중에 HashMap 구현에서 사용되는 일부 내부 개체를 반환합니다.
  • getStuff은 이름과 관계없는 다른 Stuff을 반환합니다.
  • getStuff은 무한 루프에 걸리게됩니다.

이것은 쉽게 이해할 수있는 명백한 사례입니다. 따라서 아니요, ConcurrentHashMap 또는 구아바의 MapMaker과 같이 잘 디자인 된 클래스가있는 경우 지름길을 사용하지 마십시오.

그런데 : containsKey을 먼저 호출 한 다음 get을 동일한 키로 호출하는 것은 다소 비효율적입니다. get으로 전화하여 결과를 저장하고 null과 비교하십시오. 검색 작업 하나를지도에 저장합니다.

1

HashMapConcurrentHashMap으로 대체하면 귀하의 impl은 문제가 없습니다.

더 일반적인 솔루션은 다음과 같은 일

1에 대해 걱정할 필요가 있습니다. 이름 기반 잠금, 전역 잠금이 아닙니다. create(n1) 블록 인 경우 다른 이름의 조작에는 영향을 미치지 않습니다.

2. create()이 null을 반환하거나 예외를 throw하는 경우는? 흥미롭게도 이것은 일부 impl의 경우 문제입니다.

3. create(n1) 번으로 get(n1) 번으로 전화하면 어떻게됩니까? 우리는 재귀를 가질 것입니다. 일부 impl은 정지합니다. 일부 impl은 재귀를 감지하고 오류를 던집니다.더 IMPL (또는 스택 오버 플로우를 종료하거나한다) 재귀 실행을 허용해야하고, 재귀가 종료 될 때까지의 중간 결과는, 잠금 나사에 표시되지만, 다른 스레드에 보이지 않을 것이다.

관련 문제