2017-04-21 3 views
-2

I가 (a 실패 단위 테스트와 함께) 클래스 다음 세트 :HashMap을 사용하여 캐시에서 같은 키에 동일한 객체를 반환하는 방법?

스프로킷 :

public class Sprocket { 
    private int serialNumber; 

    public Sprocket(int serialNumber) { 
     this.serialNumber = serialNumber; 
    } 

    @Override 
    public String toString() { 
     return "sprocket number " + serialNumber; 
    } 
} 

SlowSprocketFactory :

public class SlowSprocketFactory { 
    private final AtomicInteger maxSerialNumber = new AtomicInteger(); 

    public Sprocket createSprocket() { 
     // clang, click, whistle, pop and other expensive onomatopoeic operations 
     int serialNumber = maxSerialNumber.incrementAndGet(); 
     return new Sprocket(serialNumber); 
    } 

    public int getMaxSerialNumber() { 
     return maxSerialNumber.get(); 
    } 
} 

SprocketCache :

public class SprocketCache { 

    private SlowSprocketFactory sprocketFactory; 
    private Sprocket sprocket; 

    public SprocketCache(SlowSprocketFactory sprocketFactory) { 
     this.sprocketFactory = sprocketFactory; 
    } 

    public Sprocket get(Object key) { 
     if (sprocket == null) { 
      sprocket = sprocketFactory.createSprocket(); 
     } 

     return sprocket; 
    } 
} 

TestSprocketCache 단위 테스트 :

,
public class TestSprocketCache { 

    private SlowSprocketFactory sprocketFactory = new SlowSprocketFactory(); 

    @Test 
    public void testCacheReturnsASprocket() { 
     SprocketCache cache = new SprocketCache(sprocketFactory); 
     Sprocket sprocket = cache.get("key"); 
     assertNotNull(sprocket); 
    } 

    @Test 
    public void testCacheReturnsSameObjectForSameKey() { 
     SprocketCache cache = new SprocketCache(sprocketFactory); 

     Sprocket sprocket1 = cache.get("key"); 
     Sprocket sprocket2 = cache.get("key"); 

     assertEquals("cache should return the same object for the same key", sprocket1, sprocket2); 
     assertEquals("factory's create method should be called once only", 1, sprocketFactory.getMaxSerialNumber()); 
    } 
} 

TestSprocketCache 단위 테스트는 항상 내가 변경하는 경우에도 녹색 막대를 돌려 다음과 같이 다음

Sprocket sprocket1 = cache.get("key"); 
Sprocket sprocket2 = cache.get("pizza"); 

오전 내가 SprocketCache.get 내부 (키)를 HashMap.contains를 (사용한다는 것을 추측) 방법을 사용하지만 논리를 이해하는 것처럼 보일 수는 없습니다.

+0

당신은 * HashMap을 사용하고 있지 않습니다. * 문제입니다. * 당신이 무엇을 요구하고 있는지 분명하지 않습니다. – EJP

답변

0

당신이 여기에서 발생하는 문제는 get(Object) 구현이 오직 하나 개의 인스턴스가 생성 될 수 있다는 것입니다 :

public Sprocket get(Object key) { 
     // Creates object if it doesn't exist yet 
     if (sprocket == null) { 
      sprocket = sprocketFactory.createSprocket(); 
     } 

     return sprocket; 
    } 

이 전형적인 게으른 로딩 인스턴스 싱글 패턴이다. get을 다시 호출하면 sprocket에 인스턴스가 지정되고 인스턴스화를 완전히 건너 뜁니다. key 매개 변수를 전혀 사용하지 않으므로 아무런 영향을주지 않습니다. 당신의 목표 달성하기 위해 Map 실제로 하나 개의 방법이 될 것이다 사용

:

public class SprocketCache { 

    private SlowSprocketFactory sprocketFactory; 
    private Map<Object, Sprocket> instances = new HashMap<Object, Sprocket>(); 

    public SprocketCache(SlowSprocketFactory sprocketFactory) { 
     this.sprocketFactory = sprocketFactory; 
    } 

    public Sprocket get(Object key) { 
     if (!instances.containsKey(key)) { 
      instances.put(sprocket); 
     } 

     return instances.get(key); 
    } 
} 
0

글쎄, 현재 캐시 구현은 너무 당연 항상 같은 캐시 - 한 번 값도 반환하지 않는다, 키에 의존하지 않습니다.

public class SprocketCache { 

    private SlowSprocketFactory sprocketFactory; 
    private ConcurrentHashMap<Object, Sprocket> cache = new ConcurrentHashMap<?>(); 

    public SprocketCache(SlowSprocketFactory sprocketFactory) { 
     this.sprocketFactory = sprocketFactory; 
    } 

    public Sprocket get(Object key) { 
     if (!cache.contains(key)) { 
      // we only wan't acquire lock for cache seed operation rather than for every get 
      synchronized (key){      
       // kind of double check locking to make sure no other thread has populated cache while we were waiting for monitor to be released 
       if (!cache.contains(key)){ 
        cache.putIfAbsent(key, sprocketFactory.createSprocket()); 
       } 
      } 
     } 
     return cache.get(key); 
    } 
} 

커플 중요한 측면 노트 :

    당신이 키에 대한 다른 값을 저장하려면, 당신이 그것을 스레드 안전 할 가정하면

    , 당신은 이런 일을 끝낼 수 있습니다
  • 당신은 CocncurrentHashMap을 사용하여 일찍이 패러다임을 보장해야하므로 다른 스레드가 캐시가 채워 졌는지 즉시 확인할 수 있습니다.
  • 캐시 값을 새로 만들 때 동기화가 이루어져 각각의 동시 스레드 스레드가 자체 값을 생성하지 않으므로 경쟁 조건에서 이전 값을 무시합니다.
  • 동기화가 꽤 비싸므로 우리는 필요할 때만 참여할 수 밖에 없으며 동일한 경쟁 조건으로 인해 동시에 여러 모니터 스레드를 가질 수 있습니다. 그래서 다른 스레드가 이미 그 값을 채우지 않았는지 확인하기 위해 synchronized 블록 이후에 다른 검사가 필요합니다.