preface

Caffeine is a Java 8-based high-performance Cache library, based on Google’s Guava API and improved from the experience of Guava Cache and ConcurrentLinkedHashMap.

Caffeine (1) _

Java Local Cache Magic -Caffeine (2)

The performance comparison

Here is the official performance test comparison, official address :github.com/ben-manes/c…

1. 8 threads read, 100% read operation

2. 6 threads read, 2 threads write, that is 75% read operations, 25% write operations.

3. Eight threads write, 100% write operation

Bottom line: Given the comparisons above, other caching frameworks are a bit of a dud.

Caffeine features

1. Limit the cache size 2. Asynchronously load entities into the cache 3. Time-based reclamation policy 5. Reference-based reclamation policy 6. Asynchronous refreshing is performed when accessing an obsolete element in the cache 7. Notification of entity expiration or deletion 10. Write notifications that can be propagated to other data sources 11. Statistics accumulated access cache

Best practices

Add the dependent

< the dependency > < groupId > com. Making. Ben - manes. Caffeine < / groupId > < artifactId > caffeine < / artifactId > < version > 2.8.2 < / version >  </dependency>Copy the code

1. Load the policy

Caffeine offers four cache addition strategies: Manual load, auto load, Manual asynchronous load and Auto asynchronous load.

(1) Manually add

public static void manualLoad() { Cache<String, String> cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(10_000) .build(); Return null String name = cache.getifPresent ("name"); System.out.println("name:" + name); Return null name = cache.get("name", k -> "small "); System.out.println("name:" + name); // Add or update a cache element cache.put("address", "shenzhen "); String address = cache.getIfPresent("address"); System.out.println("address:" + address); }Copy the code

(2) Automatic loading

private static void autoLoad() throws InterruptedException { LoadingCache<String, String> cache = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(10, TimeUnit.MINUTES) .build(new CacheLoader<String, String>() { @Nullable @Override public String load(@NonNull String s) throws Exception { System.out.println("load:" + s); Return "Xiao Ming "; } @Override public @NonNull Map<String, String> loadAll(@NonNull Iterable<? extends String> keys) throws Exception { System.out.println("loadAll:" + keys); Map<String, String> map = new HashMap<>(); map.put("phone", "13866668888"); Map. Put ("address", "shenzhen "); return map; }}); Return null String name = cache.get("name"); return null String name = cache.get("name"); System.out.println("name:" + name); Map<String, String> Graphs = Cache.getall (arrays.asList ("phone", "address")); System.out.println(graphs); }Copy the code

(3) Manual asynchronous loading

private static void manualAsynLoad() throws ExecutionException, InterruptedException { AsyncCache<String, String> cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit. MINUTES). MaximumSize (10 _000) / / can be used to specify the thread pool. The executor (Executors. NewSingleThreadExecutor ()). BuildAsync (); // Find the cache element, if it does not exist, CompletableFuture<String> graph = cache.get("name", new Function<String, String>() { @SneakyThrows @Override public String apply(String key) { System.out.println("key:" + "+ thread.currentThread ().getName()); Thread.sleep(1000); Return "Xiao Ming "; }}); System.out.println(" getName _time:"+ system.currentTimemillis ()/1000); String name = graph.get(); System. The out. Println (" for the name: "+ name +", the time: "+ System. CurrentTimeMillis () / 1000); }Copy the code

(4) Automatic asynchronous loading

private static void autoAsynLoad() throws ExecutionException, InterruptedException { AsyncLoadingCache<String, String> cache = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(10, Timeunit.minutes) // You can choose: to asynchronously encapsulate a synchronous operation to generate cache elements. BuildAsync (new AsyncCacheLoader<String, String>() { @Override public @NonNull CompletableFuture<String> asyncLoad(@NonNull String key, @nonnull Executor Executor) {system.out.println (" thread.currentThread ().getName() "); Return to CompletableFuture.com pletedFuture (" Ming "); }}); / / can also choose to: build a asynchronous cache element operation and returns a future / /. BuildAsync ((key, executor) - > createExpensiveGraphAsync (key, executor)); Cache.get ("name").thenAccept(name->{system.out.println ("name:" + name); }); } private static CompletableFuture<String> createExpensiveGraphAsync(String key, Executor executor) { return CompletableFuture.supplyAsync(new Supplier<String>() { @Override public String get() { System.out.println(executor); System.out.println("key:" + key+", currentThread :"+ thread.currentthread ().getname () "); Return "Xiao Ming "; } }, executor); }Copy the code

2. Recycling strategy

Caffeine offers three recycling strategies: volume-based recycling, time-based recycling and reference-based recycling.

(1) Based on the capacity reclamation policy

There are two size-based reclamation strategies: one is based on capacity size and the other is based on weight size. It’s one or the other.

① Based on capacity –maximumSize

Specify a specific size for cache capacity, Caffeine. MaximumSize (long). When the cache capacity exceeds the specified size, the cache attempts to expel recent or frequently unused entries.

public static void main(String[] args) throws InterruptedException { Cache<String, String> cache = Caffeine.newBuilder() .maximumSize(1) .removalListener((String key, Object value, RemovalCause cause) -> System.out.printf("Key %s was removed (%s)%n", key, cause)) .build(); Cache. Put ("name", "xiaoming "); Println ("name:" + cache.getifPresent ("name") + ", cache size :" + cache.estimatedSize()); Cache. Put ("address", "China "); Thread.sleep(2000); Println ("name:" + cache.getifPresent ("name") + ", cache size :" + cache.estimatedSize()); }Copy the code

② Based on the weight –maximumWeight

Caffeine. MaximumWeight (long) is important and small in weight, and Caffeine. Weigher (weigher) is used to define the weight calculation method.

Public static void main(String[] args) throws InterruptedException {// Initialize the cache, Cache<Integer, Integer> Cache = Caffeine.newBuilder().maximumweight (20).weigher(new weigher <Integer, Integer>() { @Override public @NonNegative int weigh(@NonNull Integer key, @NonNull Integer value) { System.out.println("weigh:"+value); return value; } }) .removalListener((Integer key, Object value, RemovalCause cause) -> System.out.printf("Key %s was removed (%s)%n", key, cause)) .build(); cache.put(100, 10); System.out.println(cache.estimatedSize())); cache.put(200, 20); Thread.sleep(1000); System.out.println(cache.estimatedSize())); }Copy the code

(2) Based on time strategy

① Write time –expireAfterWrite

Expires after the specified time when the last write starts. If you keep writing, it never expires.

Private static void writeFixedTime() throws InterruptedException {// The timer starts after the last access or write and expires after a specified time. LoadingCache<String, String> graphs = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.SECONDS) .removalListener((String key, Object value, RemovalCause cause) -> System.out.printf("Key %s was removed (%s)%n", key, cause)) .build(key -> createExpensiveGraph(key)); String name = graphs.get("name"); System.out.println(" first fetch name:" + name); name = graphs.get("name"); System.out.println(" get name:" + name); Thread.sleep(2000); name = graphs.get("name"); System.out.println(" Get name after second delay :" + name); } private static String CreateSiveGraph (String key) {system.out.println (" restore automatic load data "); Return "Xiao Ming "; }Copy the code

② Write and access time –expireAfterAccess

The timer starts at the last write or access and expires after a specified time. If you keep accessing or writing, it never expires.

Private static void accessFixedTime() throws InterruptedException {// The timer starts after the last access or write and expires after a specified time. LoadingCache<String, String> graphs = Caffeine.newBuilder() .expireAfterAccess(3, TimeUnit.SECONDS) .removalListener((String key, Object value, RemovalCause cause) -> System.out.printf("Key %s was removed (%s)%n", key, cause)) .build(key -> createExpensiveGraph(key)); String name = graphs.get("name"); System.out.println(" first fetch name:" + name); name = graphs.get("name"); System.out.println(" get name:" + name); Thread.sleep(2000); name = graphs.get("name"); System.out.println(" Get name after second delay :" + name); } private static String CreateSiveGraph (String key) {system.out.println (" restore automatic load data "); Return "Xiao Ming "; }Copy the code

③ Custom time –expireAfter

Custom policy, time calculated by Expire implementation alone. Add, update and read time are calculated respectively.

private static void customTime() throws InterruptedException { LoadingCache<String, String> graphs = Caffeine.newBuilder() .removalListener((String key, Object value, RemovalCause cause) -> System.out.printf("Key %s was removed (%s)%n", key, cause)) .expireAfter(new Expiry<String, String>() { @Override public long expireAfterCreate(@NonNull String key, @NonNull String value, Long currentTime) {// Here currentTime is provided by Ticker and is independent of system time by default, Println (string. format("expireAfterCreate----key:%s,value:%s,currentTime:%d", key, value, currentTime)); return TimeUnit.SECONDS.toNanos(10); } @Override public long expireAfterUpdate(@NonNull String key, @NonNull String value, long currentTime, @nonNegative Long currentDuration) {// Here currentTime is provided by Ticker and is independent of system time by default, Println (string. format("expireAfterUpdate----key:%s,value:%s,currentTime:%d,currentDuration:%d", key, value, currentTime,currentDuration)); return TimeUnit.SECONDS.toNanos(3); } @Override public long expireAfterRead(@NonNull String key, @NonNull String value, long currentTime, @nonNegative Long currentDuration) {// Here currentTime is provided by Ticker and is independent of system time by default, System.out.println(string. format("expireAfterRead----key:%s,value:%s,currentTime:%d,currentDuration:%d", key, value, currentTime,currentDuration)); return currentDuration; } }) .build(key -> createExpensiveGraph(key)); String name = graphs.get("name"); System.out.println(" first fetch name:" + name); name = graphs.get("name"); System.out.println(" get name:" + name); Thread.sleep(5000); name = graphs.get("name"); System.out.println(" Get name after third delay 5 seconds :" + name); Thread.sleep(5000); name = graphs.get("name"); System.out.println(" Get name after 5 seconds :" + name); } private static String CreateSiveGraph (String key) {system.out.println (" restore automatic load data "); Return "Xiao Ming "; }Copy the code

(3) Based on reference strategy

Asynchronous loading does not support reference reclamation

(1) soft references

When GC is running out of memory, a soft reference reclamation policy is triggered.

Set the -xx :+PrintGCDetails -xmx100m parameter at JVM startup to see if GC log printing triggers the reclaim policy for soft references.

Private static void softValues() throws InterruptedException {// LoadingCache<String, byte[]> cache = Caffeine.newBuilder() .softValues() .removalListener((String key, Object value, RemovalCause cause) -> System.out.printf("Key %s was removed (%s)%n", key, cause)) .build(key -> loadDB(key)); System.out.println("1"); cache.put("name1", new byte[1024 * 1024*50]); System.gc(); System.out.println("2"); Thread.sleep(5000); cache.put("name2", new byte[1024 * 1024*50]); System.gc(); System.out.println("3"); Thread.sleep(5000); cache.put("name3", new byte[1024 * 1024*50]); System.gc(); System.out.println("4"); Thread.sleep(5000); cache.put("name4", new byte[1024 * 1024*50]); System.gc(); Thread.sleep(5000); } private static byte[] loadDB(String key) {system.out.println (" reload data automatically "); return new byte[1024*1024]; }Copy the code

(2) a weak reference

When GC occurs, a weak reference collection policy is triggered.

Set the -xx :+PrintGCDetails -xmx100m parameter at JVM startup to see if GC log printing triggers the collection policy for weak references.

private static void weakKeys() throws InterruptedException { LoadingCache<String, byte[]> cache = Caffeine.newBuilder() .weakKeys() .weakValues() .removalListener((String key, Object value, RemovalCause cause) -> System.out.printf("Key %s was removed (%s)%n", key, cause)) .build(key -> loadDB(key)); System. The out. Println (" name1 "); cache.put("name1", new byte[1024 * 1024]); System.gc(); System. The out. Println (" add name2 "); Thread.sleep(5000); cache.put("name2", new byte[1024 * 1024]); System.gc(); System. The out. Println (" add name3 "); Thread.sleep(5000); cache.put("name3", new byte[1024 * 1024]); System.gc(); Thread.sleep(5000); } private static byte[] loadDB(String key) {system.out.println (" reload data automatically "); return new byte[1024*1024]; }Copy the code

To be continued

Welcome to follow my wechat official account: CodingTao