2010-01-15 30 views
15

Tôi muốn triển khai bộ nhớ đệm đơn giản của các đối tượng hạng nặng trong ứng dụng web java. Nhưng tôi không thể tìm ra cách làm đúng cách.Thực hiện bộ nhớ cache bằng cách sử dụng java ConcurrentHashMap

Tôi có thiếu thứ gì đó hoặc các phương thức ConcurrentHashMap (putIfAbsent, ...) không đủ và cần đồng bộ hóa bổ sung không?

Có API đơn giản hơn hay không (Trong bộ nhớ lưu trữ, không có cấu hình bên ngoài) để thực hiện việc này?

P.

+1

Chỉ cần tự hỏi: yêu cầu bộ nhớ đệm của bạn thực sự là gì? Bạn có cần phải cache toàn bộ đóng cửa chuyển tiếp của đối tượng có trọng lượng nặng để nó nhất quán trên cụm máy chủ ứng dụng của bạn không? Nếu vậy, đây là vấn đề không quan trọng để giải quyết và bạn có thể sử dụng thư viện bộ nhớ cache như ehcache tốt hơn. – Alan

Trả lời

14

Nếu nó là an toàn để tạm thời có nhiều hơn một ví dụ cho điều bạn đang cố gắng để bộ nhớ cache, bạn có thể làm một bộ nhớ cache "lock-free" như thế này:

public Heavy instance(Object key) { 
    Heavy info = infoMap.get(key); 
    if (info == null) { 
    // It's OK to construct a Heavy that ends up not being used 
    info = new Heavy(key); 
    Heavy putByOtherThreadJustNow = infoMap.putIfAbsent(key, info); 
    if (putByOtherThreadJustNow != null) { 
     // Some other thread "won" 
     info = putByOtherThreadJustNow; 
    } 
    else { 
     // This thread was the winner 
    } 
    } 
    return info; 
} 

Nhiều chủ đề có thể "đua" để tạo và thêm một mục cho chìa khóa, nhưng chỉ có một người nên "thắng".

+0

Điều gì sẽ xảy ra nếu bạn muốn có phương thức cập nhật thay thế/làm mới đối tượng nặng cho một khóa nhất định? – Paolo1976

+0

Hoặc chỉ sử dụng MapMaker và chỉ một chuỗi sẽ tạo ra Nặng. Nếu một chủ đề khác cần nó trong khi nó vẫn đang ở giữa việc tạo nó, nó sẽ đơn giản chờ kết quả. –

+0

@Paolo: Tôi sẽ cho phép người bỏ phiếu từ bỏ 'MapMaker' rất thích. – Ken

0

ConcurrentHashMap nên là đủ cho nhu cầu của bạn putIfAbsent là chủ đề an toàn.

Không chắc cách đơn giản hơn nhiều bạn có thể nhận

ConcurrentMap myCache = new ConcurrentHashMap(); 

Paul

2

Thay vì đặt các "vật nặng" vào bộ nhớ cache, bạn có thể sử dụng đối tượng nhà máy ánh sáng để tạo ra một bộ nhớ cache tích cực.

public abstract class LazyFactory implements Serializable { 

    private Object _heavyObject; 

    public getObject() { 
    if (_heavyObject != null) return _heavyObject; 
    synchronized { 
     if (_heavyObject == null) _heavyObject = create(); 
    } 
    return _heavyObject; 
    } 

    protected synchronized abstract Object create(); 
} 

// here's some sample code 

// create the factory, ignore negligible overhead for object creation 
LazyFactory factory = new LazyFactory() { 
    protected Object create() { 
    // do heavy init here 
    return new DbConnection(); 
    }; 
}; 
LazyFactory prev = map.pufIfAbsent("db", factory); 
// use previous factory if available 
return prev != null ? prev.getObject() : factory.getObject; 
25

Thêm vào câu trả lời của Ken, nếu tạo một đối tượng hạng nặng mà sau đó bị bỏ đi thì KHÔNG được chấp nhận (bạn muốn đảm bảo rằng chỉ một đối tượng được tạo cho mỗi khóa), thì bạn có thể làm điều này bằng .. .. thực sự, không. Đừng tự làm. Sử dụng các google-collections (nay guava) MapMaker class:

Map<KeyType, HeavyData> cache = new MapMaker<KeyType, HeavyData>() 
    .makeComputingMap(new Function<KeyType, HeavyData>() { 
     public HeavyData apply(KeyType key) { 
      return new HeavyData(key); // Guaranteed to be called ONCE for each key 
     } 
    }); 

Sau đó, một đơn giản cache.get(key) chỉ làm việc và hoàn toàn loại bỏ bạn khỏi phải lo lắng về những khía cạnh khó khăn của đồng thời và syncrhonization.

Lưu ý rằng nếu bạn muốn thêm một số tính năng ra như hết hạn, nó chỉ là

Map<....> cache = new MapMaker<....>() 
    .expiration(30, TimeUnit.MINUTES) 
    .makeComputingMap(.....) 

và bạn cũng có thể dễ dàng sử dụng các giá trị mềm hay yếu cho một trong hai phím hoặc dữ liệu nếu cần thiết (xem Javadoc để biết thêm chi tiết)

+0

Wow, một giải pháp tốt đẹp và thanh lịch! – Benjamin

0

Tôi nhận ra đây là một bài đăng cũ, nhưng trong java 8, điều này có thể được thực hiện mà không cần tạo một đối tượng nặng có khả năng không sử dụng với ConcurrentHashMap.

public class ConcurrentCache4<K,V> { 
    public static class HeavyObject 
    { 
    } 

    private ConcurrentHashMap<String, HeavyObject> cache = new ConcurrentHashMap<>(); 

    public HeavyObject get(String key) 
    { 
     HeavyObject heavyObject = cache.get(key); 
     if (heavyObject != null) { 
      return heavyObject; 
     } 

     return cache.computeIfAbsent(key, k -> new HeavyObject()); 
    } 
} 
Các vấn đề liên quan