1. Learning Objectives

1. Master the three filling strategies of Caffeine:

  • Manually populate data
  • Synchronous loading
  • Asynchronous loading

2. Master Caffeine provides three recycling strategies:

  • Size based reclamation
  • Time-based collection
  • Reference-based reclamation

3. Remove listeners from Caffeine

If we need to be notified to generate a callback and do some extra processing when the cache is removed. This is where RemovalListener comes in handy.

Deleting the inside of a listener is performed asynchronously using Executor. The default execution is ForkJoinPool.commonPool(), which can be overridden by Caffeine. Executor (executor).

4. Caffeine’s CacheWriter

The CacheWriter allows the cache to act as a proxy for the underlying resource. When used in conjunction with the cache Reader, all reads and writes to the cache can be passed through the Writer. Writer can extend the operation cache and the operation of external resources into a synchronous atomic operation. And it will block subsequent update cache operations until the cache write is complete, but the read (get) will return the original value directly. If the write fails, the original key and value mappings remain unchanged, and if an exception occurs, they are thrown directly to the caller.

Actions that can be monitored:

  • The CacheWriter synchronously listens for cache creation, change, and deletion operations.

Actions that cannot be listened to:

  • Operations that load (for example, loadingCache.get), reload (for example, loadingCache.refresh), and calculate (for example, map.puteifPresent) are not listened on by CacheWriter.

Matters needing attention:

  • CacheWriter cannot be used with weak keys or AsyncLoadingCache.

Application Scenarios:

  • Can be used with external storage

5. Caffeine statistics

6. Caffeine refresh

  • The refresh is not done as soon as it expires, but only after the data is accessed again. Block only the thread that loaded the data; the rest of the threads return the old data.

Caffeine Introduction

Caffeine is a Java 8-based high-performance cache library that provides near-optimal hit ratios. Caffeine’s underlying version uses ConcurrentHashMap, which allows the cache to expire and then be destroyed according to certain rules or custom rules.

Caffeine’s characteristics:

  • Automatically loads data into the local cache and can be configured asynchronously;
  • Elimination strategy: elimination strategy based on quantity; Based on the dead-time elimination strategy, this time is counted from the last operation [access or write];
  • Asynchronous refresh;
  • Key will be wrapped as Weak reference; The Value is wrapped as a Weak or Soft reference so that it can be GC removed without memory leaks.
  • Data elimination reminder;
  • Write broadcast mechanism;
  • Cache access can be counted;

Caffeine’s internal structure:

  • The Cache contains a ConcurrentHashMap: This is also where all of our cached data is stored. It’s important to know that The ConcurrentHashMap is a concurrency safe container, so Caffeine is actually an enhanced ConcurrentHashMap.
  • Scheduler: A mechanism to periodically empty data. If not set, it does not automatically empty expired data.
  • Executor: Specifies the thread pool to use when running asynchronous tasks. You do not need to set it. If not, the default thread pool, ForkJoinPool.commonPool(), is used.

3. Three fill-in strategies of Caffeine

Caffeine offers three fill strategies: manual fill, synchronous load, and asynchronous load.

1. Manually fill data:

public class CaffeineManualTest {

	@Test
	public void test(a) {
		// Initialize the cache with a write expiration of 1 minute and a maximum of 100 caches
        Cache<Integer, String> cache = Caffeine.newBuilder()
                .expireAfterWrite(1, TimeUnit.MINUTES)
                .maximumSize(100)
                .build();

        int key1 = 1;

        // getIfPresent(Object key) : retrieves a value from the cache based on the key. If no value is returned, NULL is returned
        System.out.println(cache.getIfPresent(key1));

        // get(key, Function) : query a cache based on key, if not return the default value, and save to the cache
        // Cache. Put (key, value) will override the original key. Cache. Get (key, k - > value) is recommended
        System.out.println(cache.get(key1, new Function<Integer, String>() {
            @Override
            public String apply(Integer key) {
                System.out.println(key);
                return "abcd"; }})); System.out.println(cache.getIfPresent(key1));// PUT (key, value) : overwrites the data of the original key
        String value1 = "wxyz";
        cache.put(key1, value1);

        System.out.println(cache.getIfPresent(key1));

        // invalidate(key) : invalidates datacache.invalidate(key1); System.out.println(cache.getIfPresent(key1)); }}Copy the code

If multiple threads are getting at the same time, will the Function object be executed multiple times?

  • No, actually can see from chart, Caffeine is the major internal data structure is a ConcurrentHashMap, and get that ConcurrentHashMap.com is ultimately perform pute, here will be executed only once.
  • The get method blocks the call, and the Function method is called only once, even if multiple threads simultaneously request the value. This avoids competing with writes from other threads, which is why get is better than getIfPresent.

2. Synchronous loading:

Using this synchronization mechanism, the load function in the CacheLoader object reads data from the database and inserts it into the Caffeine cache when data is not available from the Caffeine cache. This mechanism is used in conjunction with the database.

public class CaffeineLoadingTest {

	/** * Get data from database **@param key
	 * @return* /
	private String getFromDataBase(int key) {
		return key + RandomStringUtils.randomAlphabetic(8);
	}

	@Test
	public void test(a) {
		// Initialize the cache with a write expiration of 1 minute and a maximum of 100 caches
		LoadingCache<Integer, String> cache = Caffeine.newBuilder()
				.expireAfterWrite(1, TimeUnit.MINUTES)
				.maximumSize(100)
				.build(
						new CacheLoader<Integer, String>() {

							/** * By default, get and getAll will each call cacheloader. load on keys that have no value in the cache. * *@param key
							 * @return* /
							@Override
							public String load(@NonNull Integer key) {
								return getFromDataBase(key);
							}

							/** * If loadAll is overridden, getAll will call the cacheloader. loadAll method on each key that has no value in the cache to build the cached value. * *@param keys
							 * @return* /
							@Override
							public @NonNull Map<Integer, String> loadAll(@NonNull Iterable<? extends Integer> keys) {
								Map<Integer, String> map = new HashMap<>();
								for (Integer key : keys) {
									map.put(key, getFromDataBase(key) + "abcd");
								}

								returnmap; }});int key1 = 100;

		// get(key, Function) : query a cache based on key, if not return the default value, and save to the cache
		// Cache. Put (key, value) will override the original key. Cache. Get (key, k - > value) is recommended
		String value1 = cache.get(key1);
		System.out.println(value1);

		// getAll(keys) : batch lookup
		Map<Integer, String> dataMap = cache.getAll(Arrays.asList(1.2.3.100)); System.out.println(dataMap); }}Copy the code

Running results:

100FDQHepbj
{1=1UwoFIuYoabcd, 2=2eDQKMwqpabcd, 3=3HYnJXUsRabcd, 100=100FDQHepbj}
Copy the code

3. Asynchronous loading:

public class CaffeineAsynchronousTest {

	/** * Get data from database **@param key
	 * @return* /
	private String getFromDataBase(int key) {
		System.out.println("2 Current thread:" + Thread.currentThread().getName());

		try {
			Thread.sleep(10 _000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		return key + RandomStringUtils.randomAlphabetic(8);
	}

	private CompletableFuture<String> getValue(Integer key) {
		System.out.println("2 Current thread:" + Thread.currentThread().getName());

		try {
			Thread.sleep(10 _000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		return CompletableFuture.supplyAsync(() -> key + RandomStringUtils.randomAlphabetic(8));
	}

	@Test
	public void test2(a) throws ExecutionException, InterruptedException {
		AsyncLoadingCache<Integer, String> asyncLoadingCache = Caffeine.newBuilder()
				.expireAfterWrite(10, TimeUnit.MINUTES)
				.maximumSize(10 _000)

				// Use executor to set the thread pool
				// .executor(Executors.newSingleThreadExecutor())

				.buildAsync(key -> getFromDataBase(key));

		// .buildAsync(key -> getValue(key).get(1, TimeUnit.SECONDS));


		Integer key = 1;

		// PUT (key, value) : overwrites the data of the original key
		// setAsyncValue(), getFromDataBase() will not be executed
		String value1 = "wxyz1";
		asyncLoadingCache.put(key, CompletableFuture.completedFuture(value1));

		System.out.println("1 Current thread:"+ Thread.currentThread().getName()); CompletableFuture<String> future = asyncLoadingCache.get(key); String value = future.get(); System.out.println(value); }}Copy the code

Four, Caffeine’s three recycling strategies

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

1. Size based reclamation:

Based on maximum cache number:

@Test
public void test(a) throws InterruptedException {
    // Initialize the cache, set 1 minute write expiration, 2 maximum number of caches
    Cache<Integer, String> cache = Caffeine.newBuilder()
            .maximumSize(2)
            .build();

    cache.put(1."a");
    System.out.println(cache.estimatedSize());

    cache.put(2."b");
    System.out.println(cache.estimatedSize());

    System.out.println(cache.getAllPresent(Lists.newArrayList(1.2)));

    // Trigger cache reclamation (asynchronous)
    cache.put(3."c");
    System.out.println(cache.estimatedSize());

    System.out.println(cache.getAllPresent(Lists.newArrayList(1.2.3)));

    // Trigger cache reclamation (asynchronous)
    cache.put(4."d");
    System.out.println(cache.estimatedSize());

    CleanUp () can wait for the asynchronous execution to complete
    // cache.cleanUp();

    Thread.sleep(1000);

    System.out.println(cache.getAllPresent(Lists.newArrayList(1.2.3.4)));
}

Copy the code

Running results:

1
2
{1=a, 2=b}
3
{1=a, 2=b, 3=c}
4
{2=b, 4=d}
Copy the code

Based on weights:

/** * based on the weight */
@Test
public void testMaximumWeight(a) {
    Cache<Integer, Integer> cache = Caffeine.newBuilder()
            // TODO specifies the maximum cache weight. Used with weigher, recent or rarely used caches are reclaimed as cache sizes approach the maximum. MaximumWeight and maximumSize cannot be used together
            .maximumWeight(11)
            // Specify the weight calculation method, triggered when the cache is created or updated
            .weigher(new Weigher<Object, Object>() {
                @Override
                public @NonNegative int weigh(@NonNull Object key, @NonNull Object value) {
                    if (value instanceof Integer) {
                        System.out.println("Is of type Integer:" + value);

                        return (Integer) value;
                    }

                    System.out.println("Not of type Integer:" + value);

                    return 0;
                }
            })
            .build();

    List<Integer> keys = new ArrayList<>();
    for (int i = 0; i < 100; i++) {
        cache.put(i, i);
        keys.add(i);
    }

    Map<Integer, Integer> map = cache.getAllPresent(keys);
    map.forEach((k, v) -> System.out.println(k + ":" + v));

    cache.cleanUp();

    System.out.println();

    map = cache.getAllPresent(keys);
    map.forEach((k, v) -> System.out.println(k + ":" + v));
    int sum = 0;
    for (Integer ele : map.keySet()) {
        sum += ele;
    }
    System.out.println("Sum of elements:" + sum );

    // Thread.sleep(1000);

    System.out.println(cache.estimatedSize());
}
Copy the code

Running results:

Integer: 0 Integer: 1 Integer: 2 Integer: 3... Yes Integer: 97 Yes Integer: 98 Yes Integer: 99 0:0 1:1 2:2 3:3... 97:97 98:98 99:99 0:0 8:8 Sum of elements :8 2Copy the code

2. Time-based recovery:

  • ExpireAfterAccess (long, TimeUnit) : Starts timing after the last access or write and expires after a specified time. If there are always requests to access the key, the cache will never expire.
  • ExpireAfterWrite (long, TimeUnit) : Starts a timer after the last write to the cache and expires after a specified time.
  • ExpireAfter (Expiry) : a custom policy. The Expiry time is calculated solely by the Expiry implementation.

Matters needing attention:

  • If both expireAfterWrite and expireAfterAccess exist at the same time, expireAfterWrite prevails.
  • The cache deletion strategy uses lazy deletion and timed deletion. The time complexity of both deletion strategies is O(1).
public class CaffeineEvictionPolicyBaseOnTimeTest {

	/** * Get data from database **@param key
	 * @return* /
	private String getFromDataBase(int key) {
		return key + RandomStringUtils.randomAlphabetic(8);
	}

	/** * Expiration policy **@throws InterruptedException
	 */
	@Test
	public void testExpirePolicy(a) throws InterruptedException {
		// Collect based on fixed expiration policy
		LoadingCache<Integer, Object> cache = Caffeine.newBuilder()
				// TODO starts timing after the last access or write and expires after the specified time. If there are always requests to access the key, the cache will never expire.
				.expireAfterAccess(5, TimeUnit.MINUTES)
				.build(key -> getFromDataBase(key));

		LoadingCache<Integer, Object> cache1 = Caffeine.newBuilder()
				// TODO starts timing after the last write to the cache and expires after the specified time.
				.expireAfterWrite(10, TimeUnit.MINUTES)
				.build(key -> getFromDataBase(key));
	}

	/** * Customize the expiration policy */
	@Test
	public void testCustomerExpirePolicy(a) {
		LoadingCache<Integer, Object> cache2 = Caffeine.newBuilder()
				.expireAfter(new Expiry<Object, Object>() {
					@Override
					public long expireAfterCreate(@NonNull Object key, @NonNull Object value, long currentTime) {
						return 0;
					}

					@Override
					public long expireAfterUpdate(@NonNull Object key, @NonNull Object value, long currentTime, @NonNegative long currentDuration) {
						return 0;
					}

					@Override
					public long expireAfterRead(@NonNull Object key, @NonNull Object value, long currentTime, @NonNegative long currentDuration) {
						return 0; } }).build(key -> getFromDataBase(key)); }}Copy the code

3. Reference based collection:

  • WeakKeys () : Uses weak references to store keys. If there is no strong reference to the key anywhere else, the cache is reclaimed by the garbage collector. Since the garbage collector only relies on identity equality, this causes the entire cache to compare keys using identity (==) equality instead of equals().
  • WeakValues () : Stores a value using a weak reference. If there is no strong reference to the value elsewhere, the cache is reclaimed by the garbage collector. Since the garbage collector only relies on identity equality, this causes the entire cache to compare keys using identity (==) equality instead of equals().
  • SoftValues () : Stores values using soft references. When memory is full, soft-referenced objects are garbage collected in a least-recently-used manner. Since using soft references requires waiting until memory is full, it is generally recommended to set a maximum memory usage for the cache. SoftValues () will compare values using identity (==) instead of equals().

Matters needing attention:

  • AsyncLoadingCache does not support weak or soft references
  • WeakValues () and Caffeine.softvalues () cannot be used together
  • LRU algorithm is used when the recycle strategy is used
public class CaffeineEvictionPolicyBaseOnReferenceTest {

	/** * Get data from database **@param key
	 * @return* /
	private String getFromDataBase(int key) {
		return key + RandomStringUtils.randomAlphabetic(8);
	}

	@Test
	public void test(a) {

		LoadingCache<Integer, String> cache1 = Caffeine.newBuilder()
				// If neither key nor value is referenced (both are null), the cache is expelled
				.weakKeys()
				.weakValues()
				.build(key -> getFromDataBase(key));


		LoadingCache<Integer, String> cache2 = Caffeine.newBuilder()
				// When the garbage collector needs to free memory.softValues() .build(key -> getFromDataBase(key)); }}Copy the code

Remove the listener from Caffeine

If we need to be notified to generate a callback and do some extra processing when the cache is removed. This is where RemovalListener comes in handy.

Deleting the inside of a listener is performed asynchronously using Executor. The default execution is ForkJoinPool.commonPool(), which can be overridden by Caffeine. Executor (executor).

public class CaffeineRemovalTest {

	@Test
	public void test(a) throws InterruptedException {
		Cache<Integer, String> cache = Caffeine.newBuilder()
				// TODO specifies the maximum number of caches. If this value is exceeded, caches that have not been accessed in a long time or are not used often will be discarded
				.maximumSize(2)
				.removalListener(new RemovalListener<Object, Object>() {
					@Override
					public void onRemoval(@Nullable Object key, @Nullable Object value, @NonNull RemovalCause cause) {
						System.out.println("[Log print] triggered" + cause.name() + "Policy, cleared key =" + key + ", value=" + value);
					}
				})
				.build();

		cache.put(1."a");
		System.out.println(cache.estimatedSize());

		cache.put(2."b");
		System.out.println(cache.estimatedSize());

		System.out.println(cache.getAllPresent(Lists.newArrayList(1.2)));

		// Trigger cache reclamation (asynchronous)
		cache.put(3."c");
		System.out.println(cache.estimatedSize());

		System.out.println(cache.getAllPresent(Lists.newArrayList(1.2.3)));

		// Trigger cache reclamation (asynchronous)
		cache.put(4."d");
		System.out.println(cache.estimatedSize());

		CleanUp () can wait for the asynchronous execution to complete
		// cache.cleanUp();

		Thread.sleep(1000);

		System.out.println(cache.getAllPresent(Lists.newArrayList(1.2.3.4)));

		cache.put(5."e");
		// Manually delete the cache
		cache.invalidate(5);

		System.out.println(cache.getAllPresent(Lists.newArrayList(1.2.3.4))); }}Copy the code

Running results:

1 2 {1=a, 2=b} 3 {1=a, 2=b, 3=c} 4 The SIZE policy was triggered, and the cleared key = 1, value=a 4= EXPLICIT key = 4, value=d {2=b} 4= EXPLICIT key = 5, value=eCopy the code

Caffeine’s CacheWriter

The CacheWriter allows the cache to act as a proxy for the underlying resource. When used in conjunction with the cache Reader, all reads and writes to the cache can be passed through the Writer. Writer can extend the operation cache and the operation of external resources into a synchronous atomic operation. And it will block subsequent update cache operations until the cache write is complete, but the read (get) will return the original value directly. If the write fails, the original key and value mappings remain unchanged, and if an exception occurs, they are thrown directly to the caller.

Actions that can be monitored:

  • The CacheWriter synchronously listens for cache creation, change, and deletion operations.

Actions that cannot be listened to:

  • Operations that load (for example, loadingCache.get), reload (for example, loadingCache.refresh), and calculate (for example, map.puteifPresent) are not listened on by CacheWriter.

Matters needing attention:

  • CacheWriter cannot be used with weak keys or AsyncLoadingCache.

Application Scenarios:

  • Can be used with external storage
public class CaffeineWriterTest {

	/** * acts as a level 2 cache with a lifetime of only the next GC */
	private Map<Integer, WeakReference<Integer>> secondCacheMap = new ConcurrentHashMap<>();

	@Test
	public void test(a) throws InterruptedException {
		LoadingCache<Integer, Integer> cache = Caffeine.newBuilder()
				.maximumSize(1)
				.writer(
						new CacheWriter<Integer, Integer>() {

							/** * writes to external storage **@param key
							 * @param value
							 */
							@Override
							public void write(@NonNull Integer key, @NonNull Integer value) {
								secondCacheMap.put(key, new WeakReference<>(value));

								System.out.println("Write to external storage, set key =" + key + "Put it in level 2 cache.");
								System.out.println("Level 2 cache:" + secondCacheMap);
								System.out.println();
							}

							/** * Delete external storage **@param key
							 * @param value
							 * @param cause
							 */
							@Override
							public void delete(@NonNull Integer key, @Nullable Integer value, @NonNull RemovalCause cause) {
								switch (cause) {
									// Manually call methods such as invalidate or remove
									case EXPLICIT:
										secondCacheMap.remove(key);
										System.out.println("Delete external storage," + cause.name() + "Strategy, set key =" + key);
										System.out.println("Level 2 cache:" + secondCacheMap);
										System.out.println();
										break;

									// The cache size is exceeded
									case SIZE:
										secondCacheMap.remove(key);
										System.out.println("Delete external storage," + cause.name() + "Strategy, set key =" + key);
										System.out.println("Level 2 cache:" + secondCacheMap);
										System.out.println();
										break;

									// Call methods such as put to modify
									case REPLACED:
										secondCacheMap.remove(key);
										System.out.println("Delete external storage," + cause.name() + "Strategy, set key =" + key);
										System.out.println("Level 2 cache:" + secondCacheMap);
										System.out.println();
										break;

									// Set the key or value reference mode
									case COLLECTED:
										secondCacheMap.remove(key);
										System.out.println("Delete external storage," + cause.name() + "Strategy, set key =" + key);
										System.out.println("Level 2 cache:" + secondCacheMap);
										System.out.println();
										break;

									// Set the expiration time
									case EXPIRED:
										secondCacheMap.remove(key);
										System.out.println("Delete external storage," + cause.name() + "Strategy, set key =" + key);
										System.out.println("Level 2 cache:" + secondCacheMap);
										System.out.println();
										break;

									default:
										break;
								}
							}
						}
				)
				.removalListener(new RemovalListener<Object, Object>() {
					@Override
					public void onRemoval(@Nullable Object key, @Nullable Object value, @NonNull RemovalCause cause) {
						System.out.println("[Log print] triggered" + cause.name() + "Policy, cleared key =" + key + ", value=" + value);
					}
				})
				.build(new CacheLoader<Integer, Integer>() {
						   /** * By default, get and getAll will each call cacheloader. load on keys that have no value in the cache. * *@param key
							* @return* /
						   @Nullable
						   @Override
						   public Integer load(@NonNull Integer key) {
							   WeakReference<Integer> value = secondCacheMap.get(key);
							   if (value == null) {
								   return 100;
							   }

							   System.out.println("Trigger CacheLoader#load() to read key = from level 2 cache" + key);
							   System.out.println();

							   returnvalue.get(); }}); cache.put(1.1);
		cache.put(2.2);

		CleanUp () can wait for the asynchronous execution to complete
		// cache.cleanUp();

		Thread.sleep(1000);

		If the default value is not returned and saved to the cache, the maximum value of the cache is exceeded, triggering a cleanup operation
		System.out.println(cache.get(1));

		Thread.sleep(1000);

		System.out.println(cache.getIfPresent(2)); }}Copy the code

Running results:

Written to the external storage, will be the key = 1 in the second level cache The second level cache: {1 = Java. Lang. Ref. 47 d384ee WeakReference @} written to external storage, will be the key = 2 in the second level cache The second level cache: {1 = Java. Lang. Ref. 47 d384ee WeakReference @, 2 = Java. Lang. Ref. 1 ff8b8f WeakReference @} deleting external storage, the SIZE policy, will be the key = 1 second level cache: {2 = Java. Lang. Ref. 1 ff8b8f WeakReference @} triggered the SIZE print log 】 【 strategy, clear the key = 1, value = 1 100 deleting external storage, the SIZE policy, will be the key = 2 level 2 cache: {} [log print] Triggered the SIZE policy, and cleared key =2, value=2 nullCopy the code

6. Caffeine statistics

public class CaffeineStatsTest {

	@Test
	public void test(a) {
		Cache<Integer, String> cache = Caffeine.newBuilder()
				.maximumSize(10 _00)
				.recordStats()
				.build();

		for (int i = 0; i < 100; i++) {
			cache.get(i, new Function<Integer, String>() {
				@Override
				public String apply(Integer integer) {
					return  RandomStringUtils.randomAlphanumeric(8); }}); }for (int i = 0; i < 100; i++) {
			if (i % 2= =0) {
				System.out.println(i);
				System.out.println(cache.getIfPresent(i));
				System.out.println();
			}
		}

		System.out.println("Average time to load a new value totalLoadTime/(loadSuccessCount + loadFailuevent):" + cache.stats().averageLoadPenalty());
		System.out.println("Number of cache ejections:" + cache.stats().evictionCount());
		System.out.println("Number of cache evictions (weight) :" + cache.stats().evictionWeight());
		System.out.println("Return total number of cache hits:" + cache.stats().hitCount());
		System.out.println(Returns the ratio of hits to requests: + cache.stats().hitRate());
		System.out.println("Total times new values were loaded:" + cache.stats().loadCount());
		System.out.println("Total number of successful loads of new values:" + cache.stats().loadSuccessCount());
		System.out.println("Total number of failed loads of new values:" + cache.stats().loadFailureCount());
		System.out.println("Ratio of failure to load new values:" + cache.stats().loadFailureRate());
		System.out.println("missCount: " + cache.stats().missCount());
		System.out.println("missRate: " + cache.stats().missRate());
		System.out.println("hitCount + missCount: " + cache.stats().requestCount());
		System.out.println("Total nanoseconds taken to load new values:"+ cache.stats().totalLoadTime()); }}Copy the code

Running results:

Average time to load a new value totalLoadTime/(loadSuccessCount + loadFailurelyto describe a series of events): 23188.66 Number of cache ejects: 0 Number of cache ejects (weight): 0 Total number of cache hits returned: 50 Returns ratio of hits to requests: 0.333333333333 Total number of times a new value is loaded: 100 Total number of times a new value is successfully loaded: 100 Total number of times a new value fails to be loaded: 0 Ratio of new value fails to be loaded: 0.0 missCount: 100 missRate: 0.666666666666 hitCount + missCount: 150 Total number of nanoseconds taken to load the new value: 2318866Copy the code

Seven, Caffeine refresh

The refresh is not done as soon as it expires, but only after the data is accessed again. Block only the thread that loaded the data; the rest of the threads return the old data.

public class CaffeineRefreshTest {

	@Test
	public void test(a) throws InterruptedException {
		System.out.println("Start time:" + LocalDateTime.now());
		System.out.println();

		LoadingCache<Integer, String> cache = Caffeine.newBuilder()
				.expireAfterWrite(Duration.ofSeconds(5))
				// TODO note that the refresh is not done as soon as it expires, but only after the data is accessed again. Block only the thread that loaded the data; the rest of the threads return the old data.
				.refreshAfterWrite(Duration.ofSeconds(2))
				.build(new CacheLoader<Integer, String>() {
						   /** * The thread that calls this method retrieves old data **@param key
							* @return
							* @throws Exception
							*/
						   @Nullable
						   @Override
						   public String load(@NonNull Integer key) throws Exception {
							   System.out.println("Refresh time:" + LocalDateTime.now());
							   System.out.println("2 Current thread:" + Thread.currentThread().getName());
							   int i = RandomUtils.nextInt(0.20);
							   System.out.println(i);
							   System.out.println();
							   if (i % 2= =0) {
								   return "wxyz";
							   } else {
								   return "WXYZ"; }}}); cache.put(1."abcd");
		String value1 = cache.get(1);
		System.out.println(First access key=1: + value1);
		System.out.println("1 Current thread:" + Thread.currentThread().getName());
		System.out.println();

		// Old data is returned for the first time after the refresh is triggered
		Thread.sleep(3000);
		System.out.println("3s access key=1:" + cache.get(1));
		System.out.println();

		// Return new data
		String value2 = cache.get(1);
		String result2 = value2.equals(value1) ? "false" : "true";
		System.out.println(Getkey =1; + value2 + ", refresh value =" + result2);
		System.out.println();

		// Old data is returned for the first time after the refresh is triggered
		Thread.sleep(3000);
		String value3 = cache.get(1);
		String result3 = value3.equals(value2) ? "false" : "true";
		System.out.println("Access key=1 after 6s:" + value3 + ", refresh value =" + result3);

		Thread.sleep(100);

		// Return new data
		String value4 = cache.get(1);
		String result4 = value4.equals(value3) ? "false" : "true";
		System.out.println(Getkey =1; + value4 + ", refresh value ="+ result4); }}Copy the code

Running results:

Start time: 2020-12-31T17:42:02.721 first access key=1: abcd 1 T17:42:05.784 2 Current thread: ForkJoinPool.com monpool-worker-1 11 3s after access key=1: abcd WXYZ, refresh value =true 6 times later access key=1: WXYZ, refresh value =false Refresh time: 2020-12-31T17:42:08.799 2 Current thread: ForkJoinPool.com monpool-worker-1 6 For the fourth time, check whether the key=1: wxyz is updatedCopy the code

Viii. Summary:

1. Master the three filling strategies of Caffeine:

  • Manually populate data
  • Synchronous loading
  • Asynchronous loading

2. Master Caffeine provides three recycling strategies:

  • Size based reclamation
  • Time-based collection
  • Reference-based reclamation

3. Remove listeners from Caffeine

If we need to be notified to generate a callback and do some extra processing when the cache is removed. This is where RemovalListener comes in handy.

Deleting the inside of a listener is performed asynchronously using Executor. The default execution is ForkJoinPool.commonPool(), which can be overridden by Caffeine. Executor (executor).

4. Caffeine’s CacheWriter

The CacheWriter allows the cache to act as a proxy for the underlying resource. When used in conjunction with the cache Reader, all reads and writes to the cache can be passed through the Writer. Writer can extend the operation cache and the operation of external resources into a synchronous atomic operation. And it will block subsequent update cache operations until the cache write is complete, but the read (get) will return the original value directly. If the write fails, the original key and value mappings remain unchanged, and if an exception occurs, they are thrown directly to the caller.

Actions that can be monitored:

  • The CacheWriter synchronously listens for cache creation, change, and deletion operations.

Actions that cannot be listened to:

  • Operations that load (for example, loadingCache.get), reload (for example, loadingCache.refresh), and calculate (for example, map.puteifPresent) are not listened on by CacheWriter.

Matters needing attention:

  • CacheWriter cannot be used with weak keys or AsyncLoadingCache.

Application Scenarios:

  • Can be used with external storage

5. Caffeine statistics

6. Caffeine refresh

  • The refresh is not done as soon as it expires, but only after the data is accessed again. Block only the thread that loaded the data; the rest of the threads return the old data.