example
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(MY_LISTENER)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
applicability
Caching is quite useful in many scenarios. For example, caching should be considered when computing or retrieving a value is expensive and requires more than one fetch for the same input.
Guava Cache is ConcurrentMap
very similar, but not exactly the same. The most basic difference is ConcurrentMap
that all added elements are kept until explicitly removed. In contrast, in order to limit memory usage, Guava Cache is usually set to automatically recycle elements. In some scenarios LoadingCache
, it is useful even though the element is not recycled, because it automatically loads the cache.
Generally speaking, Guava Cache is suitable for:
- You are willing to consume some memory space for speed.
- You expect some keys to be queried more than once.
- The total amount of data stored in the cache will not exceed the memory capacity. (Guava Cache is a local cache for a single application runtime. It does not store data to files or external servers. If this does not meet your needs, try a tool like Memcached)
If your scenario fits each of the above, Guava Cache is for you.
As shown in the sample code, Cache instances are obtained through the CacheBuilder
generator pattern, but customizing your cache is the most interesting part.
Note: If you don't need the features in the Cache, it's more memory efficient to use ConcurrentHashMap - but most features of the Cache are hard to replicate over the old ConcurrentMap, or even impossible.
load
Before using a cache, first ask yourself a question: is there a sensible default way to load or compute the value associated with a key? If there is, you should use it CacheLoader
. If not, or if you want to override the default load operation while preserving the atomic semantics of "get-cache-if-absent-compute" [get-if-absent-compute], you should get
pass in an Callable
instance when calling. Cached elements can also be Cache.put
inserted directly via methods, but autoloading is preferred because it makes it easier to infer the consistency of all cached content.
CacheLoader
LoadingCache
is CacheLoader
a built-in cache implementation. Creating your own CacheLoader
usually involves simply implementing the V load(K key) throws Exception
method. For example, you can build with the following code LoadingCache
:
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
...
try {
return graphs.get(key);
} catch (ExecutionException e) {
throw new OtherException(e.getCause());
}
LoadingCache
The normal way to query from is to use a method get(K)
. This method either returns the already cached value, or CacheLoader
atomically loads the new value into the cache using. Also declared to throw exceptions due to the CacheLoader
possibility of throwing exceptions. If your definition does not declare any checked exception, you can pass the lookup cache; but it must be noted that once a checked exception is declared, it cannot be called .LoadingCache.get(K)
ExecutionException
CacheLoader
getUnchecked(K)
CacheLoader
getUnchecked(K)
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) { // no checked exception
return createExpensiveGraph(key);
}
});
...
return graphs.getUnchecked(key);
getAll(Iterable<? extends K>)
method is used to perform batch queries. By default, for each key that is not in the cache, the getAll
method is called separately CacheLoader.load
to load the cache item. If batch loads are more efficient than multiple individual loads, you can overload CacheLoader.loadAll
to take advantage of this. getAll(Iterable)
performance will be improved accordingly.
Note: Implementations of CacheLoader.loadAll can load cached values for keys that are not explicitly requested. For example, when calculating a value for any key in a group, all key values in the group can be obtained, and the loadAll method can be implemented to obtain other key values in the group at the same time. Note:
getAll(Iterable<? extends K>)
The method will call loadAll, but it will filter the results and only return the requested key-value pair.
I don't quite understand the above sentence |
Callable
All types of Guava Cache, with or without autoloading, support the get(K, Callable) method. This method returns the corresponding value in the cache, or operates on the given Callable and adds the result to the cache. The observable state associated with the cache item does not change until the entire load method completes. This method simply implements the pattern "return if cached; otherwise compute, cache, then return".
Cache<Key, Graph> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(); // look Ma, no CacheLoader
...
try {
// If the key wasn't in the "easy to compute" group, we need to
// do things the hard way.
cache.get(key, new Callable<Key, Graph>() {
@Override
public Value call() throws AnyException {
return doThingsTheHardWay(key);
}
});
} catch (ExecutionException e) {
throw new OtherException(e.getCause());
}
explicit insertion
Use cache.put(key, value)
methods to insert values directly into the cache, which directly overwrites the previously mapped value for the given key. Cache.asMap()
The cache can also be modified using any of the methods provided by the view. Note, however, that asMap
none of the view's methods guarantee that the cached item will be loaded into the cache atomically. Furthermore, asMap
atomic operations on views are outside the scope of atomic loading in Guava Cache, so should always be used in preference to .Cache.asMap().putIfAbsent(K,
V),Cache.get(K, Callable<V>)
cache reclamation
A harsh reality is that we almost certainly don't have enough memory to cache all the data. You have to decide: when is a cache item not worth keeping? Guava Cache provides three basic cache recycling methods: capacity-based recycling, timing recycling, and reference-based recycling.
size-based eviction
Only use if you want to specify that the number of cached items does not exceed a fixed value
CacheBuilder.maximumSize(long)
. The cache will attempt to reclaim cached items that have not been used recently or are rarely used overall. - WARNING: The cache may be reclaimed before the number of cached items reaches the limit - usually, this happens when the number of cached items approaches the limit.In addition, different cache items have different "weights" - for example, if your cached values occupy completely different memory spaces, you can use
CacheBuilder.weigher(Weigher)
a weight function andCacheBuilder.maximumWeight(long)
specify a maximum total weight. In the weight-limited scenario, in addition to paying attention to the fact that the recycling is also carried out when the weight approaches the limit value, it is also necessary to know that the weight is calculated when the cache is created, so the complexity of the weight calculation must be considered.
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumWeight(100000)
.weigher(new Weigher<Key, Graph>() {
public int weigh(Key k, Graph g) {
return g.vertices().size();
}
})
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) { // no checked exception
return createExpensiveGraph(key);
}
});
Timed Eviction
CacheBuilder
There are two methods of timing recycling:expireAfterAccess(long, TimeUnit)
: The cache item is reclaimed if it has not been read/write accessed within the given time. Note that the order of evictions for this cache is the same as for size-based evictions.
expireAfterWrite(long, TimeUnit)
: The cache item is not write-accessed (created or overwritten) within the given time, then reclaimed. This recycling method is desirable if it is considered that cached data will always become stale and unavailable after a fixed amount of time.
As discussed below, timed reclamation is performed periodically on write operations and occasionally on read operations.Test timing recovery
When testing timed recycling, you don't necessarily have to spend two seconds to test for a two-second expiration. Instead of having to use the system clock, you can use
Ticker
interfaces andCacheBuilder.ticker(Ticker)
methods to customize a time source in the cache.Reference-based Eviction
By using weakly referenced keys, or weakly referenced values, or soft referenced values, Guava Cache can set up the cache to allow garbage collection:
CacheBuilder.weakKeys()
: Store keys using weak references. Cache items can be garbage collected when the key has no other (strong or soft) references. Because garbage collection relies only on identities (==), caches that use weakly referenced keys compare keys with == instead of equals.
CacheBuilder.weakValues()
: Use weak references to store values. Cache items can be garbage collected when the value has no other (strong or soft) references. Because garbage collection relies only on identities (==), caches that use weakly referenced values compare values with == instead of equals.
CacheBuilder.softValues()
: Use soft references to store values. Soft references are reclaimed in global least recently used order only when needed in response to memory. Given the performance impact of using soft references, we generally recommend using a more performance-predictive cache size limit (see above, based on capacity reclamation). Caches that use soft reference values also use == instead of equals to compare values.
explicit clear
At any time, you can explicitly clear the cache item instead of waiting for it to be reclaimed:
Individual Clear: Cache.invalidate(key)
Bulk Clear: Cache.invalidateAll(keys)
Clear All Cached Items:Cache.invalidateAll()
remove listener
With CacheBuilder.removalListener(RemovalListener)
that, you can declare a listener to do some extra action when a cached item is removed. When a cached item is removed, RemovalListener
a removal notification [ RemovalNotification
] is obtained with the reason for removal [ RemovalCause
], key, and value.
Note that RemovalListener
any exception thrown will be discarded [swallowed] after being logged.
CacheLoader<Key, DatabaseConnection> loader = new CacheLoader<Key, DatabaseConnection> () {
public DatabaseConnection load(Key key) throws Exception {
return openConnection(key);
}
};
RemovalListener<Key, DatabaseConnection> removalListener = new RemovalListener<Key, DatabaseConnection>() {
public void onRemoval(RemovalNotification<Key, DatabaseConnection> removal) {
DatabaseConnection conn = removal.getValue();
conn.close(); // tear down properly
}
};
return CacheBuilder.newBuilder()
.expireAfterWrite(2, TimeUnit.MINUTES)
.removalListener(removalListener)
.build(loader);
Warning: By default, the listener method is called synchronously when the cache is removed. Because cache maintenance and request response are usually done concurrently, expensive listener methods in synchronous mode can slow down normal cache requests. In this case, you can RemovalListeners.asynchronous(RemovalListener, Executor)
decorate the listener with an asynchronous operation.
When does the cleanup happen?
Using CacheBuilder
the built cache does not "automatically" perform cleanup and recycling, nor does it clean up immediately after a cache item expires, and there is no such cleanup mechanism. Instead, it will do a small amount of maintenance on-the-fly with writes, or occasionally with reads —if there are too few writes.
The reason for this is that if the cache is to be cleaned continuously and automatically, there must be a thread that competes with user operations for a shared lock. Additionally, thread creation may be limited in some environments and thus CacheBuilder
not available.
Instead, we put the choice in your hands. If your cache is high throughput, you don't need to worry about cache maintenance and cleanup. If your cache only has occasional writes, and you don't want cleanup to block reads, you can create your own maintenance thread that is called at regular intervals Cache.cleanUp()
. ScheduledExecutorService
It can help you implement such timing scheduling well.
refresh
Refreshing is not the same as recycling. As LoadingCache.refresh(K)
stated, a refresh means loading a new value for a key, and this process can be asynchronous. While the flush operation is in progress, the cache can still return the old value to other threads, unlike a recycle operation where the thread reading the cache has to wait for the new value to be loaded.
If the flushing process throws an exception, the cache will keep the old value, and the exception will be discarded after logging [swallowed].
Overloading CacheLoader.reload(K, V)
can extend the behavior on refresh, this method allows the developer to use the old value when calculating the new value.
//有些键不需要刷新,并且我们希望刷新是异步完成的
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.refreshAfterWrite(1, TimeUnit.MINUTES)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) { // no checked exception
return getGraphFromDatabase(key);
}
public ListenableFuture<Key, Graph> reload(final Key key, Graph prevGraph) {
if (neverNeedsRefresh(key)) {
return Futures.immediateFuture(prevGraph);
}else{
// asynchronous!
ListenableFutureTask<Key, Graph> task=ListenableFutureTask.create(new Callable<Key, Graph>() {
public Graph call() {
return getGraphFromDatabase(key);
}
});
executor.execute(task);
return task;
}
}
});
CacheBuilder.refreshAfterWrite(long, TimeUnit)
Automatic timing refresh function can be added to the cache. expireAfterWrite
Conversely, cached refreshAfterWrite
items can be kept available through scheduled refreshes, but be warned: cached items are only actually refreshed when they are retrieved (if CacheLoader.refresh
implemented asynchronously, retrievals will not be slowed down by refreshes). Therefore, if you declare expireAfterWrite
and at the same time on the cache refreshAfterWrite
, the cache will not be blindly reset periodically due to flushing. If the cache item is not retrieved, the flush will not actually happen, and the cache item will also become recyclable after the expiration time. .
Other features
statistics
CacheBuilder.recordStats()
Used to enable the statistics function of Guava Cache. When statistics is turned on, theCache.stats()
method returnsCacheStats
an object to provide the following statistics:hitRate()
: Cache hit ratio;
averageLoadPenalty()
: Average time to load a new value, in nanoseconds;
evictionCount()
: Total number of cached items being reclaimed, excluding explicit clears.
In addition, there are many other statistics. These statistics are critical for tuning cache settings, and we recommend paying close attention to these statistics in applications with high performance requirements.asMap
viewasMap
Views provide aConcurrentMap
form of caching, butasMap
the interaction of views with caches requires attention:cache.asMap()
Contains all items currently loaded into the cache. Accordingly,cache.asMap().keySet()
all currently loaded keys are included;
asMap().get(key)
essentially equivalent tocache.getIfPresent(key)
, and not causing the cache entry to be loaded. ThisMap
is consistent with the semantic convention of .
All read and write operations reset the access time of the associated cache item, includingCache.asMap().get(Object)
methods andCache.asMap().put(K, V)
methods, but notCache.asMap().containsKey(Object)
methods, norCache.asMap()
operations on collection views. For example, traversalCache.asMap().entrySet()
does not reset read times for cached items.Interrupting
cache loading methods such as Cache.get will not throwInterruptedException
. We can also make these methods supportInterruptedException
, but such support is bound to be incomplete and adds cost to all users, while only a few users actually benefit. Read on for details.Cache.get
When requesting an uncached value, there are two situations: the current thread loads the value; or it waits for another thread that is loading the value. The interrupts in these two cases are not the same. The simpler case is waiting for another thread that is loading a value: interrupt support is achieved using interruptible waits; but the case where the current thread loads a value is more complicated: since the valueCacheLoader
is loaded by the user, if it is Interruptible, then we can also support interrupts, otherwise we can't do anything.
If user-supplied CacheLoader
is interruptible, why not let Cache.get
interrupts also be supported? In a sense, it is actually supported: if CacheLoader
thrown InterruptedException
, Cache.get
it will return immediately (just like other exceptions); in addition, in the thread that loads the cached value, the interrupt will be resumed after Cache.get
catching , while in other threads is packaged .InterruptedException
InterruptedException
ExecutionException
In principle, we could unpack and change ExecutionException
to InterruptedException
, but that would cause all LoadingCache
consumers to handle interrupt exceptions, even if the ones they provide are CacheLoader
not interruptible. This may be worthwhile if you consider that all non-loading threads' waits can still be interrupted. But many caches are only used in a single thread, and their users still have to catch exceptions that cannot be thrown InterruptedException
. Even users who share the cache across threads can only interrupt their get
calls sometimes, depending on which thread made the request first.
Our guideline for this decision is to have the cache always behave as if it were loading the value on the current thread. This principle makes it easy to switch between using a cache or calculating a value every time. If the old code (the code that loads the value) is uninterruptible, then the new code (the code that uses the cache to load the value) should probably also be uninterruptible.
As mentioned above, Guava Cache supports interrupts in a sense. In another sense, Guava Cache does not support interrupts, which makes it LoadingCache
a leaky abstraction: when the loading process is interrupted, it is treated like any other exception, which is fine in most cases; but if more If two threads are waiting to load the same cache item, even if the loading thread is interrupted, it should not let all other threads fail (caught in the wrapper ExecutionException
) InterruptedException
, the correct behavior is to let one of the remaining threads retry the load. For this, we have logged a bug. However, rather than risk fixing this bug, we might put more effort into implementing another proposal AsyncLoadingCache
that returns a Future object with the correct breaking behavior.
Original article, please indicate: Reprinted from Concurrent Programming Network – ifeve.com Link to this article: [Google Guava] 3-Cache