CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-github-ben-manes-caffeine--caffeine

A high performance caching library for Java providing Google Guava-inspired API with advanced eviction policies and comprehensive features

Pending
Overview
Eval results
Files

asynchronous-caching.mddocs/

Asynchronous Caching

Caffeine provides asynchronous cache interfaces that return CompletableFuture instances for non-blocking cache operations. The async interfaces enable high-throughput applications to perform cache operations without blocking threads.

AsyncCache Interface

The AsyncCache interface provides asynchronous cache operations returning futures.

public interface AsyncCache<K, V> {
    // Retrieval operations
    CompletableFuture<V> getIfPresent(K key);
    CompletableFuture<V> get(K key, Function<? super K, ? extends V> mappingFunction);
    CompletableFuture<V> get(K key, BiFunction<? super K, ? super Executor, CompletableFuture<V>> mappingFunction);
    CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys, Function<Set<? extends K>, Map<K, V>> mappingFunction);
    
    // Storage operations
    void put(K key, CompletableFuture<V> valueFuture);
    
    // Synchronous view
    Cache<K, V> synchronous();
}

Basic Async Operations

Asynchronous Retrieval

AsyncCache<String, String> asyncCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .buildAsync();

// Simple async retrieval - returns future of value or null
CompletableFuture<String> future1 = asyncCache.getIfPresent("key1");
String value = future1.join(); // null if not present

// Async get with compute function
CompletableFuture<String> future2 = asyncCache.get("key2", k -> {
    // This computation runs asynchronously
    return "computed_" + k;
});

// Async get with executor-aware compute function  
CompletableFuture<String> future3 = asyncCache.get("key3", (key, executor) -> {
    return CompletableFuture.supplyAsync(() -> {
        // Long-running computation on provided executor
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return "async_computed_" + key;
    }, executor);
});

Asynchronous Storage

// Store a completed future
asyncCache.put("immediate", CompletableFuture.completedFuture("immediate_value"));

// Store a future that completes later
CompletableFuture<String> laterFuture = CompletableFuture.supplyAsync(() -> {
    // Simulate async computation
    try {
        Thread.sleep(500);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    return "delayed_value";
});
asyncCache.put("delayed", laterFuture);

Bulk Async Operations

Set<String> keys = Set.of("key1", "key2", "key3");

// Bulk async retrieval with computation for missing values
CompletableFuture<Map<String, String>> bulkFuture = asyncCache.getAll(keys, missingKeys -> {
    Map<String, String> result = new HashMap<>();
    for (String key : missingKeys) {
        result.put(key, "bulk_" + key);
    }
    return result;
});

Map<String, String> bulkResult = bulkFuture.join();

Working with Futures

Chaining Operations

AsyncCache<String, UserData> userCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .buildAsync();

// Chain async operations
CompletableFuture<String> userNameFuture = userCache
    .get("user123", userId -> fetchUserFromDatabase(userId))
    .thenApply(userData -> userData.getName())
    .thenApply(String::toUpperCase);

// Handle results asynchronously
userNameFuture.thenAccept(userName -> {
    System.out.println("User name: " + userName);
}).exceptionally(throwable -> {
    System.err.println("Failed to get user name: " + throwable.getMessage());
    return null;
});

Combining Multiple Cache Operations

CompletableFuture<String> user1Future = asyncCache.get("user1", this::loadUser);
CompletableFuture<String> user2Future = asyncCache.get("user2", this::loadUser);

// Combine results from multiple async cache operations
CompletableFuture<String> combinedFuture = user1Future.thenCombine(user2Future, (user1, user2) -> {
    return "Combined: " + user1 + " + " + user2;
});

String combined = combinedFuture.join();

AsyncLoadingCache Interface

The AsyncLoadingCache interface extends AsyncCache and provides automatic async loading.

public interface AsyncLoadingCache<K, V> extends AsyncCache<K, V> {
    // Automatic async loading
    CompletableFuture<V> get(K key);
    CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys);
    
    // Synchronous view
    LoadingCache<K, V> synchronous();
}

Async Loading Operations

Creating Async Loading Cache with CacheLoader

AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .buildAsync(key -> {
        // This CacheLoader runs on the default executor
        Thread.sleep(100); // Simulate work
        return "loaded_" + key;
    });

// Get value - loads asynchronously if not present
CompletableFuture<String> loadedFuture = asyncLoadingCache.get("key1");
String loadedValue = loadedFuture.join();

Creating Async Loading Cache with AsyncCacheLoader

AsyncLoadingCache<String, UserData> userAsyncCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .buildAsync(new AsyncCacheLoader<String, UserData>() {
        @Override
        public CompletableFuture<UserData> asyncLoad(String userId, Executor executor) {
            return CompletableFuture.supplyAsync(() -> {
                // Async loading logic
                return databaseService.fetchUser(userId);
            }, executor);
        }
        
        @Override
        public CompletableFuture<Map<String, UserData>> asyncLoadAll(
                Set<? extends String> userIds, Executor executor) {
            return CompletableFuture.supplyAsync(() -> {
                // Efficient bulk async loading
                return databaseService.fetchUsers(userIds);
            }, executor);
        }
    });

// Efficient bulk async loading
CompletableFuture<Map<String, UserData>> usersFuture = 
    userAsyncCache.getAll(Set.of("user1", "user2", "user3"));

Async Refresh Operations

AsyncLoadingCache<String, String> refreshingAsyncCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .refreshAfterWrite(Duration.ofMinutes(5))
    .buildAsync((key, executor) -> CompletableFuture.supplyAsync(() -> {
        // Async refresh computation
        return fetchLatestValue(key);
    }, executor));

// The cache will automatically refresh values in the background
CompletableFuture<String> valueFuture = refreshingAsyncCache.get("key1");

// Manual async refresh
CompletableFuture<String> refreshFuture = refreshingAsyncCache.synchronous().refresh("key1");

Synchronous Views

Both async cache interfaces provide synchronous views for mixed usage patterns.

AsyncCache Synchronous View

AsyncCache<String, String> asyncCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .buildAsync();

// Get synchronous view
Cache<String, String> syncView = asyncCache.synchronous();

// Use synchronous operations on the same underlying cache
asyncCache.put("async_key", CompletableFuture.completedFuture("async_value"));
String value = syncView.getIfPresent("async_key"); // "async_value"

syncView.put("sync_key", "sync_value");
CompletableFuture<String> asyncValue = asyncCache.getIfPresent("sync_key"); 
// Future completed with "sync_value"

AsyncLoadingCache Synchronous View

AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .buildAsync(key -> "loaded_" + key);

// Get synchronous loading view
LoadingCache<String, String> syncLoadingView = asyncLoadingCache.synchronous();

// Mixed usage
CompletableFuture<String> asyncLoaded = asyncLoadingCache.get("async_key");
String syncLoaded = syncLoadingView.get("sync_key");

Error Handling in Async Caches

Handling Computation Failures

AsyncCache<String, String> asyncCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .buildAsync();

CompletableFuture<String> failingFuture = asyncCache.get("error_key", key -> {
    if (key.equals("error_key")) {
        throw new RuntimeException("Computation failed");
    }
    return "success_" + key;
});

// Handle failures
failingFuture
    .thenApply(value -> "Processed: " + value)
    .exceptionally(throwable -> {
        System.err.println("Computation failed: " + throwable.getMessage());
        return "fallback_value";
    })
    .thenAccept(result -> System.out.println("Final result: " + result));

Async Loader Error Handling

AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .buildAsync((key, executor) -> {
        return CompletableFuture.supplyAsync(() -> {
            if (key.startsWith("fail_")) {
                throw new RuntimeException("Loading failed for " + key);
            }
            return "loaded_" + key;
        }, executor);
    });

// Failed loads result in exceptional completion
CompletableFuture<String> result = asyncLoadingCache.get("fail_test");
result.whenComplete((value, throwable) -> {
    if (throwable != null) {
        System.err.println("Load failed: " + throwable.getMessage());
        // Entry is not cached when loading fails
    } else {
        System.out.println("Loaded: " + value);
    }
});

Performance Considerations

Executor Configuration

// Configure custom executor for async operations
ForkJoinPool customPool = new ForkJoinPool(20);

AsyncCache<String, String> customExecutorCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .executor(customPool)
    .buildAsync();

// All async computations will use the custom executor
CompletableFuture<String> future = customExecutorCache.get("key", k -> expensiveComputation(k));

Memory Usage

Async caches store CompletableFuture instances rather than direct values:

// Memory overhead: each entry stores a CompletableFuture wrapper
AsyncCache<String, String> asyncCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .buildAsync();

// For completed values, consider using synchronous cache if memory is constrained
Cache<String, String> syncCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .build();

Thread Safety

Async caches maintain the same thread-safety guarantees as synchronous caches:

AsyncCache<String, String> threadSafeAsyncCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .buildAsync();

// Multiple threads can safely access async cache concurrently
ExecutorService executor = Executors.newFixedThreadPool(10);
List<CompletableFuture<String>> futures = new ArrayList<>();

for (int i = 0; i < 100; i++) {
    final int threadId = i;
    CompletableFuture<String> future = threadSafeAsyncCache.get("key_" + threadId, 
        k -> "computed_" + k);
    futures.add(future);
}

// Wait for all computations
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

Install with Tessl CLI

npx tessl i tessl/maven-com-github-ben-manes-caffeine--caffeine

docs

asynchronous-caching.md

cache-construction.md

cache-policies.md

functional-interfaces.md

index.md

statistics.md

synchronous-caching.md

tile.json