I have memory-expensive objects, that sometimes come with identical content. I would like to cache them as long as they're referenced at least once, and evict from the cache if all referenced are destroyed. Is there a standard solution for that in Java?
Concretely, I would like to achieve something like the following:
ExpObject can be referenced by multiple instances of Entry, and each Entry can be contained in multiple collections. (any other references to ExpObject's don't need to be tracked).
When an Entry is created, we check whether there is already an equal ExpObject and if so, return the cached copy. Otherwise we cache and return the new object. The corresponding reference counter is incremented.
Finally, when all referenced to Entry are gone and it gets collected, the reference counter in ExpObject is decremented, and if it happened to be the last Entry that pointed to this ExpObject, then the ExpObject is evicted from the cache.
However, this solution relies on the finalize() method, that I've been told I shouldn't rely on.
class ExpObject {
private final static Map<ExpObject, ExpObject> cache = new HashMap<>();
private String longString; //stand-in for large content
private int referenced = 0;
public static getExpObject(String content) {
ExpObject newObject = new ExpObject(content);
if (cache.containsKey(newObject)){
ExpObject cachedCopy = cache.get(newObject);
cachedCopy.referenced++;
return cachedCopy;
} else {
newObject.referenced++;
cache.put(newObject, newObject);
return newObject;
}
}
private ExpObject(String content){
this.content = content;
}
public void decrementReference(){
this.referenced--;
if (this.referenced <= 0){
cache.remove(this);
}
}
}
class Entry {
private ExpObject expObject;
public Entry(String content){
expObject = ExpObject.getExpObject(content);
}
#Override
protected void finalize(){
expObject.decrementReference();
}
}
Related
I have the following set of classes (along with a failing unit test):
Sprocket:
public class Sprocket {
private int serialNumber;
public Sprocket(int serialNumber) {
this.serialNumber = serialNumber;
}
#Override
public String toString() {
return "sprocket number " + serialNumber;
}
}
SlowSprocketFactory:
public class SlowSprocketFactory {
private final AtomicInteger maxSerialNumber = new AtomicInteger();
public Sprocket createSprocket() {
// clang, click, whistle, pop and other expensive onomatopoeic operations
int serialNumber = maxSerialNumber.incrementAndGet();
return new Sprocket(serialNumber);
}
public int getMaxSerialNumber() {
return maxSerialNumber.get();
}
}
SprocketCache:
public class SprocketCache {
private SlowSprocketFactory sprocketFactory;
private Sprocket sprocket;
public SprocketCache(SlowSprocketFactory sprocketFactory) {
this.sprocketFactory = sprocketFactory;
}
public Sprocket get(Object key) {
if (sprocket == null) {
sprocket = sprocketFactory.createSprocket();
}
return sprocket;
}
}
TestSprocketCache unit test:
public class TestSprocketCache {
private SlowSprocketFactory sprocketFactory = new SlowSprocketFactory();
#Test
public void testCacheReturnsASprocket() {
SprocketCache cache = new SprocketCache(sprocketFactory);
Sprocket sprocket = cache.get("key");
assertNotNull(sprocket);
}
#Test
public void testCacheReturnsSameObjectForSameKey() {
SprocketCache cache = new SprocketCache(sprocketFactory);
Sprocket sprocket1 = cache.get("key");
Sprocket sprocket2 = cache.get("key");
assertEquals("cache should return the same object for the same key", sprocket1, sprocket2);
assertEquals("factory's create method should be called once only", 1, sprocketFactory.getMaxSerialNumber());
}
}
The TestSprocketCache unit test always returns a green bar even if I change the following as follows:
Sprocket sprocket1 = cache.get("key");
Sprocket sprocket2 = cache.get("pizza");
Am guessing that I have to use a HashMap.contains(key) inside SprocketCache.get() method but can't seem to figure the logic.
The problem you're having here is that your get(Object) implementation only allows one instance to be created:
public Sprocket get(Object key) {
// Creates object if it doesn't exist yet
if (sprocket == null) {
sprocket = sprocketFactory.createSprocket();
}
return sprocket;
}
This is a typical lazy-loading instantiation singleton pattern. If you invoke get again, an instance will be assigned to sprocket and it will skip the instantiation completely. Note that you don't even use the key parameter at all, so it does not affect anything.
Using a Map would indeed be one way to achieve your objective:
public class SprocketCache {
private SlowSprocketFactory sprocketFactory;
private Map<Object, Sprocket> instances = new HashMap<Object, Sprocket>();
public SprocketCache(SlowSprocketFactory sprocketFactory) {
this.sprocketFactory = sprocketFactory;
}
public Sprocket get(Object key) {
if (!instances.containsKey(key)) {
instances.put(sprocket);
}
return instances.get(key);
}
}
Well, your current Cache implementation does not rely on key, so no wonder it always returns same cached-once value.
If you want to store different values for keys, and assuming you want it to be thread safe, you might end up doing something like this:
public class SprocketCache {
private SlowSprocketFactory sprocketFactory;
private ConcurrentHashMap<Object, Sprocket> cache = new ConcurrentHashMap<?>();
public SprocketCache(SlowSprocketFactory sprocketFactory) {
this.sprocketFactory = sprocketFactory;
}
public Sprocket get(Object key) {
if (!cache.contains(key)) {
// we only wan't acquire lock for cache seed operation rather than for every get
synchronized (key){
// kind of double check locking to make sure no other thread has populated cache while we were waiting for monitor to be released
if (!cache.contains(key)){
cache.putIfAbsent(key, sprocketFactory.createSprocket());
}
}
}
return cache.get(key);
}
}
Couple important side notes:
you'll need CocncurrentHashMap to ensure happens-before paradigm and so other thread will instantly see if cache has been filled;
new cache value creation has to be synchronized so each concurrent
thread won't generate it's own value, overriding previous values during race condition;
synchronization is quite expensive so we only wan't to engage it when needed, and due to same race condition you might get several threads holding monitor at the same time. That is why another check is required AFTER synchronized block to make sure that other thread hasn't already filled that value.
I've to implement caching with EhCache. Basic requirement is, I have to keep that cached object for fixed interval ( for now 1 hours in code below). So, I implemented the code as below:
Sample domain object:
import lombok.*;
#Getter
#Setter
#ToString
#AllArgsConstructor
public class City implements Serializable {
public String name;
public String country;
public int population;
}
Cache manager class:
import net.sf.ehcache.*;
public class JsonObjCacheManager {
private static final Logger logger = LoggerFactory.getLogger(JsonObjCacheManager.class);
private CacheManager manager;
private Cache objectCache;
public JsonObjCacheManager(){
manager = CacheManager.create();
objectCache = manager.getCache("jsonDocCache");
if( objectCache == null){
objectCache = new Cache(
new CacheConfiguration("jsonDocCache", 1000)
.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU)
.eternal(false)
.timeToLiveSeconds(60 * 60)
.timeToIdleSeconds(0)
.diskExpiryThreadIntervalSeconds(0)
.persistence(new PersistenceConfiguration().strategy(PersistenceConfiguration.Strategy.LOCALTEMPSWAP)));
objectCache.disableDynamicFeatures();
manager.addCache(objectCache);
}
}
public List<String> getKeys() { return objectCache.getKeys();}
public void clearCache(){
manager.removeAllCaches();
}
public void putInCache(String key, Object value){
try{
objectCache.put(new Element(key, value));
}catch (CacheException e){
logger.error(String.format( "Problem occurred while putting data into cache: %s", e.getMessage()));
}
}
public Object retrieveFromCache(String key){
try {
Element element = objectCache.get(key);
if(element != null)
return element.getObjectValue();
}catch (CacheException ce){
logger.error(String.format("Problem occurred while trying to retrieveSpecific from cache: %s", ce.getMessage()));
}
return null;
}
}
It caches and retrieves the values very properly. But my requirement is, I must modify the object that I retrieve from cache for given key. What I'm getting is, if I modify the object that I retrieved from cache, then cached object for that key is also getting modified.
Below is the example:
public class Application {
public static void main(String[] args) {
JsonObjCacheManager manager = new JsonObjCacheManager();
final City city1 = new City("ATL","USA",12100);
final City city2 = new City("FL","USA",12000);
manager.putInCache(city1.getName(), city1);
manager.putInCache(city2.getName(), city2);
System.out.println(manager.getKeys());
for(String key: manager.getKeys()){
System.out.println(key + ": "+ manager.retrieveFromCache(key));
}
City cityFromCache = (City) manager.retrieveFromCache(city1.getName());
cityFromCache.setName("KTM");
cityFromCache.setCountry("NPL");
System.out.println(manager.getKeys());
for(String key: manager.getKeys()){
System.out.println(key + ": "+ manager.retrieveFromCache(key));
}
}
}
The output that I'm getting is:
[ATL, FL]
ATL: City(name=ATL, country=USA, population=12100)
FL: City(name=FL, country=USA, population=12000)
[ATL, FL]
ATL: City(name=KTM, country=NPL, population=12100)
FL: City(name=FL, country=USA, population=12000)
This means, whenever I'm retrieving and modifying the object for given key, it also being reflected in cached value.
What my requirement is, the cached object for given key should not be modified. Is there any way to achieve this? Or is it not correct way to implement EhCache? Or I'm missing some fundamental principle?
I'm using EhCache V2.10.3
Thank you!
When you use a cache that is storing its data on the heap and with direct object references, you need to copy the object before using it.
In general it is good practice not to mutate a value after handing over the object reference to the cache (or anybody else beyond your control).
Some caches do have a copy mechanism to protect the cached values from modification. E.g. in EHCache3 you can add copiers, see Serializers and Copiers.
Alternatively, change your design: When you have the need to mutate the value, maybe you can split the values into two objects, one that is caches, one that contains the data that needs mutating and make the latter containing the first.
I am making a particle emitter.
Every "Rendered" object is stored in a HashSet, and when there's lots of particles on the screen, the console spits out concurrent modification exceptions. I usually have a short lifetime on these particles so they get deleted after several seconds, but I am sure this could potentially be a problem in the future. How can I fix this?
EDIT: Code:
public class UpdatedManager {
private static Set<Updated> updates = new HashSet<>();
private UpdatedManager() {}
public static Set<Updated> getUpdates() {
return new HashSet<Updated>(updates);
}
public static boolean registerUpdated(Updated u) {
return updates.add(u);
}
public static boolean unregisterUpdated(Updated u) {
return updates.remove(u);
}
public static void update() {
for (Updated up : new HashSet<Updated>(updates))
up.update();
}
public static Set<GameObject> getGameObjects() {
Set<GameObject> objs = new HashSet<>();
for (Updated up : new HashSet<Updated>(updates)) {
if (up instanceof GameObject)
objs.add((GameObject) up);
}
return objs;
}
public static Set<GameObject> getGameObjectsByName(String name) {
Set<GameObject> objs = new HashSet<>();
for (GameObject go : new HashSet<GameObject>(getGameObjects())) {
if (go.getName() != null && go.getName().equals(name))
objs.add(go);
}
return objs;
}
public static Set<Particle> getParticles() {
Set<Particle> parts = new HashSet<>();
for (Updated up : new HashSet<Updated>(updates)) {
if (up instanceof Particle)
parts.add((Particle) up);
}
return parts;
}
}
A ConcurrentModificationException means you modified the set while iterating over it. It does not mean the set is full.
For example, the following code will throw a ConcurrentModificationException:
Set<String> set = new HashSet<>();
set.add("Hello");
for(String s : set)
set.add(s+" world");
Note that you are not guaranteed to get a ConcurrentModificationException, so you should avoid catching it. You should instead fix your code so that it doesn't cause the problem.
What makes you think that the set is full?
Concurrent modification exceptions mean that the set is being accessed by different threads in an unsafe manner.
Try a synchronised set using the Collections utilities
HashSet hashSet = new HashSet();
Set set = Collections.synchronizedSet(hashSet);
or use the synchronized keyword for the method accessing the set.
Is there any way of using wildcards in #CacheEvict?
I have an application with multi-tenancy that sometimes needs to evict all the data from the cache of the tenant, but not of all tenants in the system.
Consider the following method:
#Cacheable(value="users", key="T(Security).getTenant() + #user.key")
public List<User> getUsers(User user) {
...
}
So, I would like to do something like:
#CacheEvict(value="users", key="T(Security).getTenant() + *")
public void deleteOrganization(Organization organization) {
...
}
Is there anyway to do it?
Answer is: No.
And it is no easy way to achieve what you want.
Spring Cache annotations must be simple to be easy to implement by cache provider.
Efficient caching must be simple. There is a key and value. If key is found in cache use the value, otherwise compute value and put to cache. Efficient key must have fast and honest equals() and hashcode(). Assume you cached many pairs (key,value) from one tenant. For efficiency different keys should have different hashcode(). And you decide to evict whole tenant. It is no easy to find tenant elements in cache. You have to iterate all cached pairs and discard pairs belonging to the tenant. It is not efficient. It is rather not atomic, so it is complicated and needs some synchronization. Synchronization is not efficient.
Therefore no.
But, if you find a solution tell me, because feature you want is really useful.
As with 99% of every question in the universe, the answer is: it depends. If your cache manager implements something that deals with that, great. But that doesn't seem to be the case.
If you're using SimpleCacheManager, which is a basic in-memory cache manager provided by Spring, you're probably using ConcurrentMapCache that also comes with Spring. Although it's not possible to extend ConcurrentMapCache to deal with wildcards in keys (because the cache store is private and you can't access it), you could just use it as an inspiration for your own implementation.
Below there's a possible implementation (I didn't really test it much other than to check if it's working). This is a plain copy of ConcurrentMapCache with a modification on the evict() method. The difference is that this version of evict() treats the key to see if it's a regex. In that case, it iterates through all the keys in the store and evict the ones that match the regex.
package com.sigraweb.cache;
import java.io.Serializable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.util.Assert;
public class RegexKeyCache implements Cache {
private static final Object NULL_HOLDER = new NullHolder();
private final String name;
private final ConcurrentMap<Object, Object> store;
private final boolean allowNullValues;
public RegexKeyCache(String name) {
this(name, new ConcurrentHashMap<Object, Object>(256), true);
}
public RegexKeyCache(String name, boolean allowNullValues) {
this(name, new ConcurrentHashMap<Object, Object>(256), allowNullValues);
}
public RegexKeyCache(String name, ConcurrentMap<Object, Object> store, boolean allowNullValues) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(store, "Store must not be null");
this.name = name;
this.store = store;
this.allowNullValues = allowNullValues;
}
#Override
public final String getName() {
return this.name;
}
#Override
public final ConcurrentMap<Object, Object> getNativeCache() {
return this.store;
}
public final boolean isAllowNullValues() {
return this.allowNullValues;
}
#Override
public ValueWrapper get(Object key) {
Object value = this.store.get(key);
return toWrapper(value);
}
#Override
#SuppressWarnings("unchecked")
public <T> T get(Object key, Class<T> type) {
Object value = fromStoreValue(this.store.get(key));
if (value != null && type != null && !type.isInstance(value)) {
throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value);
}
return (T) value;
}
#Override
public void put(Object key, Object value) {
this.store.put(key, toStoreValue(value));
}
#Override
public ValueWrapper putIfAbsent(Object key, Object value) {
Object existing = this.store.putIfAbsent(key, value);
return toWrapper(existing);
}
#Override
public void evict(Object key) {
this.store.remove(key);
if (key.toString().startsWith("regex:")) {
String r = key.toString().replace("regex:", "");
for (Object k : this.store.keySet()) {
if (k.toString().matches(r)) {
this.store.remove(k);
}
}
}
}
#Override
public void clear() {
this.store.clear();
}
protected Object fromStoreValue(Object storeValue) {
if (this.allowNullValues && storeValue == NULL_HOLDER) {
return null;
}
return storeValue;
}
protected Object toStoreValue(Object userValue) {
if (this.allowNullValues && userValue == null) {
return NULL_HOLDER;
}
return userValue;
}
private ValueWrapper toWrapper(Object value) {
return (value != null ? new SimpleValueWrapper(fromStoreValue(value)) : null);
}
#SuppressWarnings("serial")
private static class NullHolder implements Serializable {
}
}
I trust that readers know how to initialize the cache manager with a custom cache implementation. There's lots of documentation out there that shows you how to do that. After your project is properly configured, you can use the annotation normally like so:
#CacheEvict(value = { "cacheName" }, key = "'regex:#tenant'+'.*'")
public myMethod(String tenant){
...
}
Again, this is far from being properly tested, but it gives you a way to do what you want. If you're using another cache manager, you could extends its cache implementation similarly.
Below worked for me on Redis Cache.
Suppose you want to delete all Cache entries with key prefix: 'cache-name:object-name:parentKey'. Call method with key value cache-name:object-name:parentKey*.
import org.springframework.data.redis.core.RedisOperations;
...
private final RedisOperations<Object, Object> redisTemplate;
...
public void evict(Object key)
{
redisTemplate.delete(redisTemplate.keys(key));
}
From RedisOperations.java
/**
* Delete given {#code keys}.
*
* #param keys must not be {#literal null}.
* #return The number of keys that were removed.
* #see Redis Documentation: DEL
*/
void delete(Collection<K> keys);
/**
* Find all keys matching the given {#code pattern}.
*
* #param pattern must not be {#literal null}.
* #return
* #see Redis Documentation: KEYS
*/
Set<K> keys(K pattern);
Include the tenant as part of the cache name, by implementing a custom CacheResolver; extending and implementing SimpleCacheResolver.getCacheName
then do evict all keys
#CacheEvict(value = {CacheName.CACHE1, CacheName.CACHE2}, allEntries = true)
But note that if you are using redis as your backing cache, then under the hood spring uses the KEYS command, so the solution will not scale. Once you get few 100K keys in redis, KEYS will take 150ms and the redis server will bottleneck on CPU. Naughty spring.
I had a similar issue as well. I solved it that way.
My Config Class
#Bean
RedisTemplate redisTemplate() {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(lettuceConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new RedisSerializerGzip());
return template;
}
My Util Class
public class CacheService {
final RedisTemplate redisTemplate;
public void evictCachesByPrefix(String prefix) {
Set<String> keys = redisTemplate.keys(prefix + "*");
for (String key : keys) {
redisTemplate.delete(key);
}
}
}
Warning: consider KEYS as a command that should only be used in
production environments with extreme care. It may ruin performance
when it is executed against large databases.
https://redis.io/commands/keys
I wanted to remove all stored orders from cache and i complited it this way.
#CacheEvict(value = "List<Order>", allEntries = true)
As i understand this way will be removed all lists stored with this value. So you can create another structure and it also can be a kind of solution.
I solved this by leaving the AOP-Pattern in this special case.
read remains annotation-driven:
#Cacheable(value = "imageCache", keyGenerator = "imageKeyGenerator", unless="#result == null")
public byte[] getImageData(int objectId, int imageType, int width, int height, boolean sizeAbsolute) {
// ...
}
public boolean deleteImage(int objId, int type) {
removeFromCacheByPrefix("imageCache", ImageCacheKeyGenerator.generateKey(objId, type));
int rc = jdbcTemplate.update(SQL_DELETE_IMAGE, new Object[] {objId,type});
return rc > 0;
}
as you can see, the deleteImage(...) has no annotation, but calls removeFromCacheByPrefix(...).
this is a function in the superclass of the repository which looks like this:
protected void removeFromCacheByPrefix(String cacheName, String prefix) {
var cache = this.cacheManager.getCache(cacheName);
Set<String> keys = new HashSet<String>();
cache.forEach(entry -> {
var key = String.valueOf(entry.getKey());
if (key.startsWith(prefix)) {
keys.add(String.valueOf(entry.getKey()));
}
});
cache.removeAll(keys);
}
works fine for me this way!
My add to hashtable method fails, what have i done wrong? Or what have i missunderstood?
test:
#Test
public void testAddKeyValue() {
AdminController cont = new AdminController();
Apartment o1 = new Apartment(1, 4, "Maier B", true);
ArrayList<Expense> exp = new ArrayList<>();
cont.addKeyWithList(o1, exp);
assertTrue(cont.isEmpty()); // ISSUE > the test works if it is true, but it is supposed be False.
}
repo class:
public class Repository extends HashMap<Apartment, ArrayList<Expense>>{
private Map<Apartment,ArrayList<Expense>> dic; // last expense object refers to curret month
Iterator<Map.Entry<Apartment, ArrayList<Expense>>> it;
public void addKeyWithList(Apartment apt, ArrayList<Expense> exp){
dic.put(apt, exp);
}
}
Why is my test not working? Or where in the code have I done something wrong?
Don't extend HashMap as you're doing. Use a HashMap and delegate to it:
public class Repository {
private Map<Apartment, List<Expense>> dic = new HashMap<Apartment, List<Expense>>();
public void addKeyWithList(Apartment apt, ArrayList<Expense> exp){
dic.put(apt, exp);
}
public boolean isEmpty() {
return dic.isEmpty();
}
}
At the moment, Repository is a HashMap, but you don't store anything in it: you store the values in another HashMap contained in Repository.
Also, storing an iterator in a field is a bad idea. iterators can be used only once. Once they have iterated, the can't iterate anymore. It should be a local variable.