2013-05-29 22 views
6

Pahalı nesneler oluşturmayı ve onları Map'da önbelleğe almayı içeren bazı üçüncü taraf kitaplık koduyla uğraşıyorum. Varolan uygulama farklı keys için Foos bağımsız oluşturulabilir zamanAnahtar sözcük engelleme Java'daki Harita

lock.lock() 
try { 
    Foo result = cache.get(key); 
    if (result == null) { 
     result = createFooExpensively(key); 
     cache.put(key, result); 
    } 
    return result; 
} finally { 
    lock.unlock(); 
} 

gibi bir şey Açıkçası bu en iyi tasarım olmadığı.

Bulunduğum kesmek kullanmaktır bir MapFutures ait:

lock.lock(); 
Future<Foo> future; 
try { 
    future = allFutures.get(key); 
    if (future == null) { 
     future = executorService.submit(new Callable<Foo>() { 
      public Foo call() { 
       return createFooExpensively(key); 
      } 
     }); 
     allFutures.put(key, future); 
    } 
} finally { 
    lock.unlock(); 
} 

try { 
    return future.get(); 
} catch (InterruptedException e) { 
    throw new MyRuntimeException(e); 
} catch (ExecutionException e) { 
    throw new MyRuntimeException(e); 
} 

Ama bu görünüyor ... biraz hacky, iki nedenden dolayı:

  1. eser keyfi toplanmış yapılır iplik. numaralı işin, bu anahtarı almayı deneyen ilk iş parçacığına sahip olmasından memnuniyet duyarız, özellikle de bu nedenle engellenecektir.
  2. Map tam olarak doldurulduğunda bile, sonuçlarına ulaşmak için hala Future.get() geçer. Bunun çok ucuz olduğunu umuyorum ama çirkin.

Ne istediğinizi anahtar bir değere sahip kadar belirli bir anahtar için alır engeller bir Map ile cache yerine, ancak diğer bu arada alır sağlamaktır. Böyle bir şey var mı? Veya birisiFutures için daha temiz bir alternatif var mı?

+2

Mağaza anahtar nesneleri ve anahtar nesneler kendilerini kilidi:

final PerKeySynchronizedExecutor<KEY_CLASS> executor = new PerKeySynchronizedExecutor<>(); 

kullanın:

Sınıfındaki bunu beyan? Anahtarlar intrinsikse (int, 'String' vb.) Sarın. –

+2

Bu neredeyse bir Guava ['Striped'] istediğiniz gibi geliyor (http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/util/concurrent/Striped.html), nesnelerden kilitlere bir harita gibi davranır. –

cevap

7

Anahtar başına bir kilit oluşturma sesi cazip geliyor, ancak özellikle de tuş sayısı büyük olduğunda istediğiniz şey olmayabilir.

Her anahtar için ayrı bir (okuma-yazma) kilit oluşturmanız gerekebileceğinden, bellek kullanımınız üzerinde etkisi vardır. Ayrıca, eşzamanlılık gerçekten çok yüksekse, bu ince taneciklik, sınırlı sayıda çekirdeğin bir noktaya çarpmasına neden olabilir.

ConcurrentHashMap çoğu zaman böyle bir durumda yeterince iyi bir çözümdür. Normalde tam okuyucu eşzamanlılık sağlar (normalde okuyucular engellemez) ve güncellemeler eşzamanlı olarak istenilen eşzamanlılık seviyesine kadar olabilir. Bu size oldukça iyi ölçeklenebilirlik sağlar. Yukarıdaki kod gibi ConcurrentHashMap ile ifade edilebilir, aşağıdaki: ConcurrentHashMap sisteminin kolay kullanımı çok parçacığı anahtar önbelleğe olduğunu fark olabilir ve her createFooExpensively çağırabildiği olduğunu bir dezavantajı, var

ConcurrentMap<Key,Foo> cache = new ConcurrentHashMap<>(); 
... 
Foo result = cache.get(key); 
if (result == null) { 
    result = createFooExpensively(key); 
    Foo old = cache.putIfAbsent(key, result); 
    if (old != null) { 
    result = old; 
    } 
} 

() . Sonuç olarak, bazı iş parçacıkları atma işini yapabilir. Bunu önlemek için "Java Concurrency in Practice" bölümünde belirtilen notları kullanmalısınız.

Ama

sonra tekrar, Google'da güzel millet zaten CacheBuilder şeklinde sizin için bu sorunları çözüldü: http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/cache/CacheBuilder.html

LoadingCache<Key,Foo> cache = CacheBuilder.newBuilder(). 
    concurrencyLevel(32). 
    build(new CacheLoader<Key,Foo>() { 
    public Foo load(Key key) { 
     return createFooExpensively(key); 
    } 
    }); 

... 
Foo result = cache.get(key); 
+0

Teşekkürler - JCiP'deki 'Memoizer' bölümü neredeyse tam olarak bu senaryodan geçiyor. RTFB olmalı. :) –

+0

(Ve Guava'ya erişim ile kendi kodumuza geri döndüğümde 'CacheBuilder' hatırlayacağım.) –

1

Sen funtom-java-utils kullanabilirsiniz - PerKeySynchronizedExecutor.

Her anahtar için bir kilit oluşturacak, ancak kullanılmadığında hemen sizin için temizleyecektir.Aynı anahtarla yapılan çağrılar arasında bellek görünürlüğü de verecek ve çok hızlı olacak şekilde tasarlanacak ve farklı anahtarlar arasındaki çağrılar arasındaki çekişmeyi en aza indirecek şekilde tasarlanacaktır. Bir `ConcurrentHashMap` içinde

Foo foo = executor.execute(key,() -> createFooExpensively());