SoftReferences should be deleted automatically. #777

pull/787/head
Nikita 8 years ago
parent 07ae51add5
commit 57aea881c5

@ -38,7 +38,7 @@ import io.netty.util.internal.PlatformDependent;
public abstract class AbstractCacheMap<K, V> implements Cache<K, V> { public abstract class AbstractCacheMap<K, V> implements Cache<K, V> {
final int size; final int size;
final ConcurrentMap<K, CachedValue> map = PlatformDependent.newConcurrentHashMap(); final ConcurrentMap<K, CachedValue<K, V>> map = PlatformDependent.newConcurrentHashMap();
private final long timeToLiveInMillis; private final long timeToLiveInMillis;
private final long maxIdleInMillis; private final long maxIdleInMillis;
@ -52,11 +52,11 @@ public abstract class AbstractCacheMap<K, V> implements Cache<K, V> {
this.timeToLiveInMillis = timeToLiveInMillis; this.timeToLiveInMillis = timeToLiveInMillis;
} }
protected void onValueRead(CachedValue value) { protected void onValueRead(CachedValue<K, V> value) {
} }
protected void onValueRemove(CachedValue value) { protected void onValueRemove(CachedValue<K, V> value) {
} }
@ -89,7 +89,7 @@ public abstract class AbstractCacheMap<K, V> implements Cache<K, V> {
throw new NullPointerException(); throw new NullPointerException();
} }
CachedValue entry = map.get(key); CachedValue<K, V> entry = map.get(key);
if (entry == null) { if (entry == null) {
return false; return false;
} }
@ -113,8 +113,8 @@ public abstract class AbstractCacheMap<K, V> implements Cache<K, V> {
throw new NullPointerException(); throw new NullPointerException();
} }
for (Map.Entry<K, CachedValue> entry : map.entrySet()) { for (Map.Entry<K, CachedValue<K, V>> entry : map.entrySet()) {
CachedValue cachedValue = entry.getValue(); CachedValue<K, V> cachedValue = entry.getValue();
if (cachedValue.getValue().equals(value)) { if (cachedValue.getValue().equals(value)) {
if (cachedValue.isExpired()) { if (cachedValue.isExpired()) {
if (map.remove(cachedValue.getKey(), cachedValue)) { if (map.remove(cachedValue.getKey(), cachedValue)) {
@ -139,7 +139,7 @@ public abstract class AbstractCacheMap<K, V> implements Cache<K, V> {
throw new NullPointerException(); throw new NullPointerException();
} }
CachedValue entry = map.get(key); CachedValue<K, V> entry = map.get(key);
if (entry == null) { if (entry == null) {
return null; return null;
} }
@ -153,8 +153,7 @@ public abstract class AbstractCacheMap<K, V> implements Cache<K, V> {
return readValue(entry); return readValue(entry);
} }
@SuppressWarnings("unchecked") protected V readValue(CachedValue<K, V> entry) {
protected V readValue(CachedValue entry) {
onValueRead(entry); onValueRead(entry);
return (V) entry.getValue(); return (V) entry.getValue();
} }
@ -168,17 +167,16 @@ public abstract class AbstractCacheMap<K, V> implements Cache<K, V> {
return put(key, value, timeToLiveInMillis, TimeUnit.MILLISECONDS, maxIdleInMillis, TimeUnit.MILLISECONDS); return put(key, value, timeToLiveInMillis, TimeUnit.MILLISECONDS, maxIdleInMillis, TimeUnit.MILLISECONDS);
} }
@SuppressWarnings("unchecked")
@Override @Override
public V put(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) { public V put(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
CachedValue entry = create(key, value, ttlUnit.toMillis(ttl), maxIdleUnit.toMillis(maxIdleTime)); CachedValue<K, V> entry = create(key, value, ttlUnit.toMillis(ttl), maxIdleUnit.toMillis(maxIdleTime));
if (isFull(key)) { if (isFull(key)) {
if (!removeExpiredEntries()) { if (!removeExpiredEntries()) {
onMapFull(); onMapFull();
} }
} }
onValueCreate(entry); onValueCreate(entry);
CachedValue prevCachedValue = map.put(key, entry); CachedValue<K, V> prevCachedValue = map.put(key, entry);
if (prevCachedValue != null) { if (prevCachedValue != null) {
onValueRemove(prevCachedValue); onValueRemove(prevCachedValue);
if (!prevCachedValue.isExpired()) { if (!prevCachedValue.isExpired()) {
@ -188,17 +186,17 @@ public abstract class AbstractCacheMap<K, V> implements Cache<K, V> {
return null; return null;
} }
protected CachedValue create(K key, V value, long ttl, long maxIdleTime) { protected CachedValue<K, V> create(K key, V value, long ttl, long maxIdleTime) {
return new StdCachedValue(key, value, ttl, maxIdleTime); return new StdCachedValue<K, V>(key, value, ttl, maxIdleTime);
} }
protected void onValueCreate(CachedValue entry) { protected void onValueCreate(CachedValue<K, V> entry) {
} }
private boolean removeExpiredEntries() { protected boolean removeExpiredEntries() {
boolean removed = false; boolean removed = false;
// TODO optimize // TODO optimize
for (CachedValue value : map.values()) { for (CachedValue<K, V> value : map.values()) {
if (value.isExpired()) { if (value.isExpired()) {
if (map.remove(value.getKey(), value)) { if (map.remove(value.getKey(), value)) {
onValueRemove(value); onValueRemove(value);
@ -232,10 +230,9 @@ public abstract class AbstractCacheMap<K, V> implements Cache<K, V> {
* (non-Javadoc) * (non-Javadoc)
* @see java.util.Map#remove(java.lang.Object) * @see java.util.Map#remove(java.lang.Object)
*/ */
@SuppressWarnings("unchecked")
@Override @Override
public V remove(Object key) { public V remove(Object key) {
CachedValue entry = map.remove(key); CachedValue<K, V> entry = map.remove(key);
if (entry != null) { if (entry != null) {
onValueRemove(entry); onValueRemove(entry);
if (!entry.isExpired()) { if (!entry.isExpired()) {
@ -298,9 +295,9 @@ public abstract class AbstractCacheMap<K, V> implements Cache<K, V> {
abstract class MapIterator<M> implements Iterator<M> { abstract class MapIterator<M> implements Iterator<M> {
final Iterator<Map.Entry<K, CachedValue>> keyIterator = map.entrySet().iterator(); final Iterator<Map.Entry<K, CachedValue<K, V>>> keyIterator = map.entrySet().iterator();
Map.Entry<K, CachedValue> mapEntry; Map.Entry<K, CachedValue<K, V>> mapEntry;
@Override @Override
public boolean hasNext() { public boolean hasNext() {
@ -309,7 +306,7 @@ public abstract class AbstractCacheMap<K, V> implements Cache<K, V> {
} }
mapEntry = null; mapEntry = null;
while (keyIterator.hasNext()) { while (keyIterator.hasNext()) {
Map.Entry<K, CachedValue> entry = keyIterator.next(); Map.Entry<K, CachedValue<K, V>> entry = keyIterator.next();
if (entry.getValue().isExpired()) { if (entry.getValue().isExpired()) {
continue; continue;
} }

@ -0,0 +1,34 @@
/**
* Copyright 2016 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.cache;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
public class CachedValueReference<V> extends SoftReference<V> {
private final CachedValue<?, ?> owner;
public CachedValueReference(CachedValue<?, ?> owner, V referent, ReferenceQueue<? super V> q) {
super(referent, q);
this.owner = owner;
}
public CachedValue<?, ?> getOwner() {
return owner;
}
}

@ -15,6 +15,8 @@
*/ */
package org.redisson.cache; package org.redisson.cache;
import java.lang.ref.ReferenceQueue;
/** /**
* *
* @author Nikita Koksharov * @author Nikita Koksharov
@ -24,12 +26,26 @@ package org.redisson.cache;
*/ */
public class SoftCacheMap<K, V> extends AbstractCacheMap<K, V> { public class SoftCacheMap<K, V> extends AbstractCacheMap<K, V> {
private final ReferenceQueue<V> queue = new ReferenceQueue<V>();
public SoftCacheMap(long timeToLiveInMillis, long maxIdleInMillis) { public SoftCacheMap(long timeToLiveInMillis, long maxIdleInMillis) {
super(0, timeToLiveInMillis, maxIdleInMillis); super(0, timeToLiveInMillis, maxIdleInMillis);
} }
protected CachedValue create(K key, V value, long ttl, long maxIdleTime) { protected CachedValue<K, V> create(K key, V value, long ttl, long maxIdleTime) {
return new SoftCachedValue(key, value, ttl, maxIdleTime); return new SoftCachedValue<K, V>(key, value, ttl, maxIdleTime, queue);
}
@Override
protected boolean removeExpiredEntries() {
while (true) {
CachedValueReference<V> value = (CachedValueReference<V>) queue.poll();
if (value == null) {
break;
}
map.remove(value.getOwner().getKey(), value.getOwner());
}
return super.removeExpiredEntries();
} }
@Override @Override

@ -15,36 +15,25 @@
*/ */
package org.redisson.cache; package org.redisson.cache;
import java.lang.ref.SoftReference; import java.lang.ref.ReferenceQueue;
/** /**
* Created by jribble on 2/20/17. * Created by jribble on 2/20/17.
*/ */
public class SoftCachedValue<K, V> implements CachedValue<K, V> { public class SoftCachedValue<K, V> extends StdCachedValue<K, V> implements CachedValue<K, V> {
StdCachedValue<K, SoftReference<V>> value;
public SoftCachedValue(K key, V value, long ttl, long maxIdleTime) { private final CachedValueReference<V> ref;
this.value = new StdCachedValue<K, SoftReference<V>>(key, new SoftReference<V>(value), ttl, maxIdleTime);
}
@Override public SoftCachedValue(K key, V value, long ttl, long maxIdleTime, ReferenceQueue<V> queue) {
public boolean isExpired() { super(key, null, ttl, maxIdleTime);
return value.isExpired(); this.ref = new CachedValueReference<V>(this, value, queue);
}
@Override
public K getKey() {
return value.getKey();
} }
@Override @Override
public V getValue() { public V getValue() {
return value.getValue().get(); super.getValue();
return ref.get();
} }
@Override
public String toString() {
return value.toString();
}
} }

@ -62,6 +62,7 @@ public class SoftCacheMapTest {
assertThat(map.values().stream().filter(Objects::nonNull).count()).isEqualTo(100000); assertThat(map.values().stream().filter(Objects::nonNull).count()).isEqualTo(100000);
System.gc(); System.gc();
assertThat(map.values().stream().filter(Objects::nonNull).count()).isZero(); assertThat(map.values().stream().filter(Objects::nonNull).count()).isZero();
assertThat(map.values().size()).isZero();
} }
} }

Loading…
Cancel
Save