This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-04-10
Channels
- # aleph (3)
- # announcements (1)
- # architecture (16)
- # bangalore-clj (1)
- # beginners (65)
- # biff (5)
- # calva (23)
- # clj-kondo (6)
- # clj-otel (12)
- # clojure-austin (2)
- # clojure-europe (11)
- # clojure-norway (7)
- # clojure-uk (1)
- # clojuredesign-podcast (2)
- # clojurescript (18)
- # conjure (3)
- # datomic (1)
- # deps-new (18)
- # events (1)
- # hyperfiddle (14)
- # java (4)
- # malli (5)
- # off-topic (10)
- # pathom (13)
- # polylith (10)
- # practicalli (1)
- # re-frame (3)
- # reitit (16)
- # releases (1)
- # rum (5)
- # shadow-cljs (17)
As someone who comes from the land of single threadedness, I have been playing around with multi-threaded examples in my spare time to grow my understanding and uncover my assumptions about multi-threaded environments. One such experiment is a cache for CompletableFutures
. If I just use ConcurrentHashMap#computeIfAbsent
, my machine can run one billion iterations of a simple completable future in about 22 seconds. However, the moment I add eviction logic, that time increases to about 36 minutes. Is there any way I can make the following more efficient? As far as I can tell, synchronization is required for this logic, so I’m not sure that it can be improved (at least not by much):
public CompletableFuture<T> removeIfExpired(String key) {
synchronized(cache) {
CacheEntry<T> cached = cache.get(key);
if (cached == null || cached.getExpiresInMs() == null) {
return null;
}
if (Duration.between(cached.getCreatedAt(), Instant.now()).toMillis() >= cached.getExpiresInMs()) {
CacheEntry<T> removed = cache.remove(key);
if (removed != cached) {
throw new IllegalStateException("Concurrency error: item removed from cache not the original item checked for expiration.");
}
return removed.getValue();
}
return null;
}
}
If you use the version of remove that takes a key and value then I don't believe you need the synchronize block