Skip to content
Snippets Groups Projects
Commit a0956258 authored by Tobias Pietzsch's avatar Tobias Pietzsch
Browse files

Basic SoftRefCache with removal is working.

The interface still needs a lot of work...
There should be different Cache implementations, with or without
RemovalListeners, and with global or per-entry Loader/Remover.
parent b8666815
No related branches found
No related tags found
No related merge requests found
......@@ -23,6 +23,8 @@ public interface Cache< K, V >
*/
V get( K key, Callable< ? extends V > loader ) throws ExecutionException;
V get( K key, Callable< ? extends V > loader, Remover< ? super K, ? super V > remover ) throws ExecutionException;
void invalidateAll();
// void cleanUp();
......
package bdv.cache.revised;
public interface Remover< K, V >
{
public void remove( K key, V value );
}
package bdv.cache.revised;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
......@@ -13,6 +15,8 @@ public class SoftRefCache< K, V > implements Cache< K, V >
final ReferenceQueue< V > queue = new ReferenceQueue<>();
final ReferenceQueue< V > phantomQueue = new ReferenceQueue<>();
final class CacheSoftReference extends SoftReference< V >
{
private final Entry entry;
......@@ -25,7 +29,45 @@ public class SoftRefCache< K, V > implements Cache< K, V >
public void clean()
{
map.remove( entry.key, entry );
entry.remove();
}
}
static final class CachePhantomReference< V > extends PhantomReference< V >
{
static Field referent = null;
{
try
{
referent = Reference.class.getDeclaredField( "referent" );
}
catch ( NoSuchFieldException | SecurityException e )
{
e.printStackTrace();
}
referent.setAccessible( true );
}
SoftRefCache< ?, V >.Entry entry;
public CachePhantomReference( final V referent, final ReferenceQueue< V > remove, final SoftRefCache< ?, V >.Entry entry )
{
super( referent, remove );
this.entry = entry;
}
@SuppressWarnings( "unchecked" )
public V resurrect()
{
try
{
return ( V ) referent.get( this );
}
catch ( IllegalArgumentException | IllegalAccessException e )
{
e.printStackTrace();
return null;
}
}
}
......@@ -35,12 +77,18 @@ public class SoftRefCache< K, V > implements Cache< K, V >
private SoftReference< V > ref;
private CachePhantomReference< V > phantomRef;
private Remover< ? super K, ? super V > remover;
boolean loaded;
public Entry( final K key )
{
this.key = key;
this.ref = new SoftReference<>( null );
this.phantomRef = null;
this.remover = null;
this.loaded = false;
}
......@@ -54,11 +102,34 @@ public class SoftRefCache< K, V > implements Cache< K, V >
this.loaded = true;
this.ref = new CacheSoftReference( value, this );
}
public void setValue( final V value, final Remover< ? super K, ? super V > remover )
{
this.loaded = true;
this.ref = new CacheSoftReference( value, this );
this.phantomRef = new CachePhantomReference<>( value, phantomQueue, this );
this.remover = remover;
}
public synchronized void remove()
{
if ( remover != null )
{
final V value = phantomRef.resurrect();
phantomRef.clear();
phantomRef = null;
remover.remove( key, value );
remover = null;
// map.remove( key, this ); // TODO: this should be in here when non-removal Cache is split out
}
map.remove( key, this );
}
}
@Override
public V getIfPresent( final Object key )
{
processRemovalQueue();
final Entry entry = map.get( key );
return entry == null ? null : entry.getValue();
}
......@@ -66,6 +137,7 @@ public class SoftRefCache< K, V > implements Cache< K, V >
@Override
public V get( final K key, final Callable< ? extends V > loader ) throws ExecutionException
{
processRemovalQueue();
final Entry entry = map.computeIfAbsent( key, ( k ) -> new Entry( k ) );
V value = entry.getValue();
if ( value == null )
......@@ -81,8 +153,7 @@ public class SoftRefCache< K, V > implements Cache< K, V >
* The entry was already loaded, but its value has been
* garbage collected. We need to create a new entry
*/
map.remove( key, entry );
value = get( key, loader );
entry.remove();
}
}
else
......@@ -105,6 +176,60 @@ public class SoftRefCache< K, V > implements Cache< K, V >
}
cleanUp( 50 );
}
if ( value == null )
value = get( key, loader );
return value;
}
@Override
public V get( final K key, final Callable< ? extends V > loader, final Remover< ? super K, ? super V > remover ) throws ExecutionException
{
processRemovalQueue();
final Entry entry = map.computeIfAbsent( key, ( k ) -> new Entry( k ) );
V value = entry.getValue();
if ( value == null )
{
synchronized ( entry )
{
if ( entry.loaded )
{
value = entry.getValue();
if ( value == null )
{
/*
* The entry was already loaded, but its value has been
* garbage collected. We need to create a new entry
*/
entry.remove();
}
}
else
{
try
{
value = loader.call();
entry.setValue( value, remover );
}
catch ( final InterruptedException e )
{
Thread.currentThread().interrupt();
throw new ExecutionException( e );
}
catch ( final Exception e )
{
throw new ExecutionException( e );
}
}
}
cleanUp( 50 );
}
if ( value == null )
value = get( key, loader, remover );
return value;
}
......@@ -130,6 +255,23 @@ public class SoftRefCache< K, V > implements Cache< K, V >
return i;
}
public void processRemovalQueue()
{
int j = 0;
while ( true )
{
@SuppressWarnings( "unchecked" )
final CachePhantomReference< V > pr = ( CachePhantomReference< V > ) phantomQueue.poll();
if ( pr == null )
break;
pr.entry.remove();
pr.clear();
++j;
}
if ( j != 0 )
System.out.println( "processed " + j + " references" );
}
@Override
public void invalidateAll()
{
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment