2009-07-17 4 views
3

의 내가 가진 다음을 가정 해 봅시다 (그래서 자바 1.4에는 제네릭을 제한하지 맡기) : 웹 애플리케이션 서버와 같은 부하가 높은 멀티 스레드 환경에서정적 캐시를로드하는 가장 좋은 패턴 또는 메서드는 무엇입니까?

public class CacheManager { 
    static HashMap states; 
    static boolean statesLoaded; 

    public static String getState(String abbrev) { 
     if(!statesLoaded) { 
      loadStates(); 
     } 
     return (String) states.get(abbrev); 
    } 

    private static void loadStates() { 
     //JDBC stuff to load the data 
     statesLoaded = true; 
    } 
} 

이 이론적으로 문제가있을 수 있습니다> 1 개 스레드 동시에 캐시를 가져오고로드하려고 시도합니다. (캐시를 초기화하기 위해 웹 응용 프로그램에 시작 코드가 없다고 가정하십시오)

간단히 Collections.synchronizedMap을 사용하여이를 수정하면됩니까? 많은 스레드가 스레드에 액세스하는 경우 get()을 수행 할 때 반환 된 synchronizedMap에 성능 문제가 있습니까?

아니면 동기화되지 않은 HashMap을 가지고 대신로드 메소드 또는 부울 변수를 동기화하는 것이 더 좋을까요? 나는 그 중 하나를 동기화하면 클래스를 잠글 수 있다고 생각합니다.

예를 들어,로드 메소드가 동기화 된 경우, 2 개의 스레드가 동시에 getStates() 메소드를 입력하면 둘 다 statesLoaded가 false임을 알 수 있습니다. 첫 번째 메소드에서 메소드를 잠그고 캐시를로드하고 statesLoaded를 true로 설정합니다. 불행히도, 두 번째 스레드는 statesLoaded가 false라고 이미 평가하고 잠금이 해제되면 load 메소드로 진행합니다. 캐시를 다시로드하지 않습니까?

if(!statesLoaded) { 
    loadStates(); 
} 

이유 : 당신은이 검사를 동기화해야합니다

답변

1

? 여러 스레드가 아무런 문제없이지도에 get() 수 있습니다. 그러나 을 기본적으로으로 설정하고 statesLoaded 플래그를 확인하고 상태를로드 한 다음 플래그를 설정하고 확인하십시오. 그렇지 않으면 상태를로드 할 수 있지만 플래그는 여전히 설정되지 않고 다른 스레드에서 볼 수 있습니다.

(잠재적으로 이것을 비동기로 두어 여러 스레드가 캐시를 다시 초기화 할 수는 있지만, 적어도 좋은 프로그래밍 방법은 아니며 최악의 경우 큰 캐시를 사용하여 문제가 발생할 수 있습니다. 다른 구현 등)

결과적으로 동기화 된 맵을 갖는 것만으로는 충분하지 않습니다. (이것은 흔히 오해입니다, btw).

동기화의 성능 영향에 대해 걱정하지 않겠습니다. 이전에는 문제 였지만 지금은 훨씬 가벼운 작업입니다. 항상 그렇듯이 필요할 때 측정하고 최적화하십시오. 조기 최적화는 종종 낭비되는 노력입니다.

+0

그럼 잠금 개체는 무엇이되어야합니까? 국가 HashMap? 병목 현상을 일으키지 않을까요? 이 CacheManager 내부에 여러 개의 HashMap이 있으면 어떻게 될까요? 부하 체크의 락 오브젝트로서 특정의 HashMap를 사용하면 (자), static 클래스 전체를 잠글 수 없습니까. – user26270

+0

그래서 당신은 포함 된 객체를 자물쇠로 잠그거나 간단한 객체를 제공 할 수 있습니다 : Object lock = new Object(); 이 방법에 대한 자물쇠가 될 것입니다. –

+0

get()을 잠그지 않는 것을 잊지 마세요. –

0

직접 시도하지 마십시오. Spring이나 Guide와 같은 IoC 컨테이너를 사용하고 싱글 톤을 관리하고 초기화하는 프레임 워크를 얻습니다. 이렇게하면 동기화 문제를 훨씬 쉽게 관리 할 수 ​​있습니다.

0

싱글 톤 패턴에 문제가 있습니까? 당신이 바로 initalization 논리를 건너 뛸 경우 statesLoaded은 내가 먼저 statesLoaded을 확인 솔루션을 가고 싶어 false에서 true로 갈 수 있기 때문에

public class CacheManager { 

    private static class SingletonHolder 
    { 
     static final HashMap states; 
     static 
     { 
      states = new HashMap(); 
      states.put("x", "y"); 
     } 
    } 

    public static String getState(String abbrev) { 
     return (String) SingletonHolder.states.get(abbrev); 
    } 

} 
0

사실이다. 만약 당신이 잠그고 다시 확인하고 그것이 여전히 거짓이라면 상태를로드하고 플래그를 true로 설정하십시오.

즉, 캐시 초기화 후 getState를 호출하는 모든 스레드는 "초기에"잠금을 사용하지 않고 맵을 사용합니다.같은

뭔가 :

// If we safely know the states are loaded, don't even try to lock 
if(!statesLoaded) { 
    // I don't even pretend I know javas synchronized syntax :) 
    lock(mutex); 
    // This second check makes sure we don't initialize the 
    // cache multiple times since it might have changed 
    // while we were waiting for the mutex 
    if(!statesLoaded) { 
    initializeStates(); 
    statesLoaded = true; 
    } 
    release(mutex); 
} 
// Now you should know that the states are loaded and they were only 
// loaded once. 

이 잠금은 이전과 실제 initalization이 발생하는 동안 관여된다는 것을 의미합니다.

이것이 C라면 컴파일러가 두 번째 검사를 최적화 할 수 있도록 statesLoadedvariable을 휘발성으로 설정해야합니다. 그런 상황에서 Java가 어떻게 작동하는지 모르겠지만 동기화 범위에 들어갈 때 statesLoaded와 같은 모든 공유 데이터가 잠재적으로 더럽다고 생각합니다.

6

이 경우 캐시를로드하는 가장 좋은 방법은 JVM 정적 초기화를 활용하는 것입니다

캐시 클래스를 사용하고 처음으로로드하고 정적 초기화 이후됩니다

public class CacheManager { 
    private static final HashMap states = new HashMap(); 

    public static String getState(String abbrev) { 
     return (String) states.get(abbrev); 
    } 

    static { 
     //JDBC stuff to load the data 
    } 
} 
스레드로부터 안전하다면지도가 안전하게 채워집니다. 값을 검색하는 모든 후속 호출은 잠금이 수반되지 않고 수행 될 수 있습니다.

가능할 때마다 정적 초기화를 이용하는 것이 좋습니다. 안전하고 효율적이며 때로는 매우 간단합니다.

0

+1 IoC 컨테이너의 경우. 봄을 사용하십시오. CacheManager 클래스를 정적이 아닌 클래스로 만들고 Spring 컨텍스트 config에서 CacheManaget을 정의하십시오.

1 비 정적 CacheManager 버전

package your.package.CacheManager; 

// If you like annotation 
@Component 
public class CacheManager<K, V> { 

    private Map<K, V> cache; 

    public V get(K key) { 
     if(cache != null) { 
      return cache.get(key); 
     } 
     synchronized(cache) { 
      if(cache == null) { 
       loadCache(); 
      } 
      return cache.get(key); 
     } 
    } 

    private void loadCache() { 
     cache = new HashMap<K, V>(); 
     // Load from JDBC or what ever you want to load 
    } 
} 

2 구성 요소 주석 @ (주석 스캔 경로를 정의 foget하지 않음)/스프링 컨텍스트 또는 사용 @ 서비스에 CacheManager의 빈 정의를

<bean id="cacheManager" class="your.package.CacheManager"/> 

3 스프링 구성 또는 @Autowire 주석으로 원하는 캐쉬 빈을 삽입하십시오.

<bean id="cacheClient" clas="..."> 
    <property name="cache" ref="cacheManager"/> 
</bean> 
+0

나는 synchronized (cache)가 NullPointerException을 줄 것이라고 생각한다. 서면 코드 –

+0

은 스레드로부터 안전하지 않습니다. ref : http : //www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html –

관련 문제