2013-05-29 38 views
6

Tôi đang xử lý một số mã thư viện của bên thứ ba có liên quan đến việc tạo các đối tượng đắt tiền và lưu chúng trong một Map. Việc thực hiện hiện tại là cái gì đó nhưBản đồ chặn từng phím trong Java

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

Rõ ràng đây không phải là thiết kế tốt nhất khi Foos cho khác nhau keys thể được tạo ra một cách độc lập.

Hack hiện tại của tôi là sử dụng một Map của Futures:

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); 
} 

Nhưng điều này dường như ... một chút hacky, vì hai lý do:

  1. Công việc được thực hiện trên một gộp tùy ý chủ đề. Tôi rất vui khi có công việc được thực hiện trên chuỗi đầu tiên cố gắng lấy khóa cụ thể đó, đặc biệt là kể từ khi nó sẽ bị chặn.
  2. Ngay cả khi Map được điền đầy đủ, chúng tôi vẫn xem qua số Future.get() để nhận được kết quả. Tôi hy vọng điều này là khá rẻ, nhưng nó xấu xí.

Những gì tôi muốn là để thay thế cache với một Map rằng sẽ chặn được cho một chìa khóa cho đến khi chính mà có giá trị, nhưng cho phép khác được khi đó. Có bất kỳ điều như vậy tồn tại? Hoặc ai đó có một giải pháp thay thế sạch hơn cho Map của Futures?

+2

Store đối tượng chủ chốt trong một 'ConcurrentHashMap' và khóa trên các đối tượng chính bản thân? Nếu các khóa là nội tại (int, 'String', v.v.) bọc chúng lại. –

+2

Điều này gần giống như bạn muốn có một ổi ['sọc'] (http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/util/concurrent/Striped.html), loại hành vi như một bản đồ từ các đối tượng đến khóa. –

Trả lời

7

Tạo khóa cho mỗi phím có vẻ hấp dẫn, nhưng nó có thể không phải là thứ bạn muốn, đặc biệt khi số lượng khóa lớn.

Như bạn có thể cần phải tạo khóa chuyên dụng (đọc-ghi) cho mỗi khóa, nó có tác động đến việc sử dụng bộ nhớ của bạn. Ngoài ra, mức độ chi tiết cao đó có thể đạt đến một điểm lợi nhuận giảm dần do số lượng lõi hữu hạn nếu đồng thời thực sự cao.

ConcurrentHashMap thỉnh thoảng là giải pháp đủ tốt trong tình huống như thế này. Nó cung cấp đồng thời đọc thông thường đồng thời (thông thường người đọc không chặn), và cập nhật có thể được đồng thời lên đến mức độ đồng thời cấp mong muốn. Điều này mang lại cho bạn khả năng mở rộng khá tốt. Đoạn mã trên có thể được thể hiện với ConcurrentHashMap như sau:

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; 
    } 
} 

Việc sử dụng đơn giản của ConcurrentHashMap không có một nhược điểm, đó là nhiều chủ đề có thể thấy rằng chìa khóa không được lưu trữ, và mỗi thể gọi createFooExpensively() . Kết quả là, một số chủ đề có thể làm việc vất vả. Để tránh điều này, bạn sẽ muốn sử dụng mẫu ghi nhớ được đề cập trong "Java Concurrency in Practice".

Nhưng sau đó một lần nữa, các folks đẹp tại Google đã giải quyết những vấn đề này cho bạn theo hình thức CacheBuilder: 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

Xin cảm ơn - phần "Người ghi nhớ" trong JCiP đi qua gần như chính xác kịch bản này. Nên có RTFB tôi đoán. :) –

+0

(Và tôi sẽ nhớ 'CacheBuilder' khi tôi quay lại mã riêng của chúng tôi với quyền truy cập vào ổi.) –

1

Bạn có thể sử dụng funtom-java-utils - PerKeySynchronizedExecutor.

Nó sẽ tạo khóa cho mỗi khóa nhưng sẽ xóa khóa cho bạn ngay lập tức khi nó không được sử dụng.

Nó cũng sẽ cấp phát khả năng hiển thị bộ nhớ giữa các lời gọi với cùng một khóa và được thiết kế rất nhanh và giảm thiểu sự tranh chấp giữa các lời gọi tắt các khóa khác nhau.

Khai báo nó trong lớp học của bạn:

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

Sử dụng nó:

Foo foo = executor.execute(key,() -> createFooExpensively()); 
Các vấn đề liên quan