在项目中,如果想把某些数据放到内存缓存中,并且支持自动过期,则可以用guava cache。
要用到的类和接口有CacheBuilder、CacheLoader、Cache、LoadingCache,其中LoadingCache是Cache的子接口。
Cache接口方法有:
V get(K key, Callable<V> loader) throws ExecutionException:从缓存中取指定key对应的值,如果不存在就调用loader加载进缓存,再返回。如果同时有多个线程调用此get()方法,且恰好此时缓存中没有对应的值,则只会有一个线程去加载数据,其他线程会等待,直到缓存中有数据,读取并返回。这样的操作模式,虽然比多个线程都去加载数据好一些,但是依然有些问题。假如并发大的话,会hang住所有线程,导致系统在一定时间内不能提供服务。
示例:
public class Test { public static void main(String[] args) throws ExecutionException { Cache<String, Object> cache = CacheBuilder.newBuilder().build(); Object object = cache.get("first", Test::getFirst); System.out.println(object); object = cache.get("first", Test::getFirst); System.out.println(object); } private static List<String> getFirst() { System.out.println("getFirst"); return Lists.newArrayList(UUID.randomUUID().toString(), UUID.randomUUID().toString()); } }
V getIfPresent(Object key):从缓存中取指定key对应的值,如果不存在就返回null,而不会去加载数据。
ImmutableMap getAllPresent(Iterable keys):从缓存中取多个key对应的值。如果某个key对应的值不存在,则返回的Map不包含此键。极端情况下,返回一个空Map。
void put(K, V):把一个键值对放到缓存中。不推荐用这个方法,推荐优先使用get(K key, Callable<V> loader)方法。
void putAll(Map<K, V> map):把一堆键值对放到缓存中。
void invalidate(Object key):从缓存中移除某个key对应的值。
void invalidateAll(Iterable keys):从缓存中移除多个key对应的值。
void invalidateAll():从缓存中移除所有key对应的值,也就是清空缓存。
long size():返回缓存中key的个数。是个大概数,不完全精确。
CacheStats stats():返回一个CacheStats实例,通过这个实例可以得到命中缓存次数、未命中缓存的次数等等,前提是创建Cache时调用了CacheBuilder的recordStats()方法。我们可以用这个做缓存的监控。
示例:
public static void main(String[] args) throws ExecutionException { Cache<String, String> cc = CacheBuilder.newBuilder().recordStats().build(); cc.get("a", () -> UUID.randomUUID().toString()); cc.get("b", () -> UUID.randomUUID().toString()); ImmutableMap ss = cc.getAllPresent(Sets.newHashSet("a", "b", "c", "d")); System.out.println(ss); }
注意,Cache没有get(K key)方法,用getIfPresent(K key)就行。
LoadingCache接口继承了Cache接口,除了继承自Cache接口的全部方法外,自己定义的方法有:
V get(K key) throws ExecutionException:从缓存中获取key对应的值,如果不存在,就调用CacheLoader实例的方法加载数据进缓存。同样的,如果有多个线程同时get同一个key,也只会有一个线程去加载,其他线程会阻塞,直到缓存中有数据,读取并返回。如果有多个线程同时get多个key,则每个key都会有一个线程去加载对应数据,其他多余线程阻塞直至对应key有数据,读取并返回。
V getUnchecked(K key):效果同get(K key)方法,只是不抛ExecutionException异常。
ImmutableMap<K, V> getAll(Iterable<K> keys) throws ExecutionException:从缓存中获取多个key对应的值,哪个key对应的数据不存在,会去加载这个key对应的数据。
void refresh(K key):重新加载key对应的数据进缓存。refresh会根据当前缓存中有没有对应的数据执行不同的逻辑:如果当前缓存中有对应的数据,则会调用CacheLoader实例的reload()方法。否则会调用CacheLoader实例的load()方法。在refresh期间,如果有读请求,则会返回老值。如果没有老值,则会阻塞,等待refresh完成。如果reload()或者load()是异步的,则可能会返回null。
ConcurrentMap asMap():查看缓存中数据的视图。
示例:
LoadingCache<Integer, Map<Integer, List<Long>>> allChannelPhotoCache = CacheBuilder.newBuilder() .concurrencyLevel(8) .initialCapacity(8) .maximumSize(100) .build(new CacheLoader<Integer, Map<Integer, List<Long>>>() { @Override public Map<Integer, List<Long>> load(Integer key) { monitorLogger.info("currentThread= " + Thread.currentThread().getName() + ", load cache key= " + key); if (1 == key) { return getChannelPhotoIds(); } else { return new HashMap<>(0); } } } );
通过CacheBuilder.newBuilder().build(CacheLoader loader)创建一个Cache实例,具体类型是LocalCache类中的LocalLoadingCache内部类。
CacheBuilder.newBuilder()生成一个CacheBuilder实例。CacheBuilder有很多实例方法:
CacheBuilder concurrencyLevel(int concurrencyLevel):设置更新缓存的并发度,默认是4。会根据并发度的值把哈希表分段,每个段都有一个分段锁,用于控制对这一段数据的更新。
CacheBuilder initialCapacity(int initialCapacity):设置哈希表的初始容量,默认值是16。
CacheBuilder maximumSize(int maximumSize):设置哈希表最多能放的键值对数量。