这是一个完美的解决方案,适用于没有生命周期管理机制的API,但是您正在使用需要显式生命周期管理的东西来实现它。
考虑将一个简单的地图移动到数据库中。放弃映射引用时,没有显式的“关闭”操作。然而,如果您已经实现了直写缓存,那么您希望能够完成任何写操作并关闭到“数据库”的套接字连接。
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class provides a way for tracking the loss of reference of one type of
* object to allow a secondary reference to be used to perform some cleanup
* activity. The most common use of this is with one object which might
* contain or refer to another object that needs some cleanup performed
* when the referer is no longer referenced.
*
* An example might be an object of type Holder, which refers to or uses a
* Socket connection. When the reference is lost, the socket should be
* closed. Thus, an instance might be created as in
*
* ReferenceTracker trker = ReferenceTracker() {
* public void released( Socket s ) {
* try {
* s.close();
* } catch( Exception ex ) {
* log.log( Level.SEVERE, ex.toString(), ex );
* }
* }
* };
*
* Somewhere, there might be calls such as the following.
* * interface Holder {
* public T get();
* }
* class SocketHolder implements Holder {
* Socket s;
* public SocketHolder( Socket sock ) {
* s = sock;
* }
* public Socket get() {
* return s;
* }
* }
*
* This defines an implementation of the Holder interface which holds
* a reference to Socket objects. The use of the trker
* object, above, might then include the use of a method for creating
* the objects and registering the references as shown below.
* * public SocketHolder connect( String host, int port ) throws IOException {
* Socket s = new Socket( host, port );
* SocketHolder h = new SocketHolder( s );
* trker.trackReference( h, s );
* return h;
* }
*
* Software wishing to use a socket connection, and pass it around would
* use SocketHolder.get() to reference the Socket instance, in all cases.
* then, when all SocketHolder references are dropped, the socket would
* be closed by the released(java.net.Socket) method shown
* above.
*
* The {@link ReferenceTracker} class uses a {@link PhantomReference} to the first argument as
* the key to a map holding a reference to the second argument. Thus, when the
* key instance is released, the key reference is queued, can be removed from
* the queue, and used to remove the value from the map which is then passed to
* released().
*/
public abstract class ReferenceTracker {
/**
* The thread instance that is removing entries from the reference queue, refqueue, as they appear.
*/
private volatile RefQueuePoll poll;
/**
* The Logger instance used for this instance. It will include the name as a suffix
* if that constructor is used.
*/
private static final Logger log = Logger.getLogger(ReferenceTracker.class.getName());
/**
* The name indicating which instance this is for logging and other separation of
* instances needed.
*/
private final String which;
/**
* Creates a new instance of ReferenceTracker using the passed name to differentiate
* the instance in logging and toString() implementation.
* @param which The name of this instance for differentiation of multiple instances in logging etc.
*/
public ReferenceTracker( String which ) {
this.which = which;
}
/**
* Creates a new instance of ReferenceTracker with no qualifying name.
*/
public ReferenceTracker( ) {
this.which = null;
}
/**
* Provides access to the name of this instance.
* @return The name of this instance.
*/
@Override
public String toString() {
if( which == null ) {
return super.toString()+": ReferenceTracker";
}
return super.toString()+": ReferenceTracker["+which+"]";
}
/**
* Subclasses must implement this method. It will be called when all references to the
* associated holder object are dropped.
* @param val The value passed as the second argument to a corresponding call to {@link #trackReference(Object, Object) trackReference(T,K)}
*/
public abstract void released( K val );
/** The reference queue for references to the holder objects */
private final ReferenceQueuerefqueue = new ReferenceQueue();
/**
* The count of the total number of threads that have been created and then destroyed as entries have
* been tracked. When there are zero tracked references, there is no queue running.
*/
private final AtomicInteger tcnt = new AtomicInteger();
private volatile boolean running;
/**
* A Thread implementation that polls {@link #refqueue} to subsequently call {@link released(K)}
* as references to T objects are dropped.
*/
private class RefQueuePoll extends Thread {
/**
* The thread number associated with this instance. There might briefly be two instances of
* this class that exists in a volatile system. If that is the case, this value will
* be visible in some of the logging to differentiate the active ones.
*/
private final int mycnt;
/**
* Creates an instance of this class.
*/
public RefQueuePoll() {
setDaemon( true );
setName( getClass().getName()+": ReferenceTracker ("+which+")" );
mycnt = tcnt.incrementAndGet();
}
/**
* This method provides all the activity of performing refqueue.remove()
* calls and then calling released(K) to let the application release the
* resources needed.
*/
public @Override void run() {
try {
doRun();
} catch( Throwable ex ) {
log.log( done ? Level.INFO : Level.SEVERE,
ex.toString()+": phantom ref poll thread stopping", ex );
} finally {
running = false;
}
}
private volatile boolean done = false;
private void doRun() {
while( !done ) {
Reference ref = null;
try {
running = true;
ref = refqueue.remove();
K ctl;
synchronized( refmap ) {
ctl = refmap.remove( ref );
done = actCnt.decrementAndGet() == 0;
if( log.isLoggable( Level.FINE ) ) {
log.log(Level.FINE, "current act refs={0}, mapsize={1}", new Object[]{actCnt.get(), refmap.size()});
}
if( actCnt.get() != refmap.size() ) {
Throwable ex = new IllegalStateException("count of active references and map size are not in sync");
log.log(Level.SEVERE, ex.toString(), ex);
}
}
if( log.isLoggable( Level.FINER ) ) {
log.log(Level.FINER, "reference released for: {0}, dep={1}", new Object[]{ref, ctl});
}
if( ctl != null ) {
try {
released( ctl );
if( log.isLoggable( Level.FINE ) ) {
log.log(Level.FINE, "dependant object released: {0}", ctl);
}
} catch( RuntimeException ex ) {
log.log( Level.SEVERE, ex.toString(), ex );
}
}
} catch( Exception ex ) {
log.log( Level.SEVERE, ex.toString(), ex );
} finally {
if( ref != null ) {
ref.clear();
}
}
}
if( log.isLoggable( Level.FINE ) ) {
log.log(Level.FINE, "poll thread {0} shutdown for {1}", new Object[]{mycnt, this});
}
}
}
/**
* A count of the active references.
*/
private final AtomicInteger actCnt = new AtomicInteger();
/**
* Map from T References to K objects to be used for the released(K) call
*/
private final ConcurrentHashMap,K>refmap = new ConcurrentHashMap,K>();
/**
* Adds a tracked reference. dep should not refer to ref in any way except possibly
* a WeakReference. dep is almost always something referred to by ref.
* @throws IllegalArgumentException of ref and dep are the same object.
* @param dep The dependent object that needs cleanup when ref is no longer referenced.
* @param ref the object whose reference is to be tracked
*/
public void trackReference( T ref, K dep ) {
if( ref == dep ) {
throw new IllegalArgumentException( "Referenced object and dependent object can not be the same" );
}
PhantomReference p = new PhantomReference( ref, refqueue );
synchronized( refmap ) {
refmap.put( p, dep );
if( actCnt.getAndIncrement() == 0 || running == false ) {
if( actCnt.get() > 0 && running == false ) {
if (log.isLoggable(Level.FINE)) {
log.fine("starting stopped phantom ref polling thread");
}
}
poll = new RefQueuePoll();
poll.start();
if( log.isLoggable( Level.FINE ) ) {
log.log( Level.FINE, "poll thread #{0} created for {1}", new Object[]{tcnt.get(), this});
}
}
}
}
/**
* This method can be called if the JVM that the tracker is in, is being
* shutdown, or someother context is being shutdown and the objects tracked
* by the tracker should now be released. This method will result in
* {@link #released(Object) released(K) } being called for each outstanding refernce.
*/
public void shutdown() {
Listrem;
// Copy the values and clear the map so that released
// is only ever called once, incase GC later evicts references
synchronized( refmap ) {
rem = new ArrayList( refmap.values() );
refmap.clear();
}
for( K dep : rem ) {
try {
released( dep );
} catch( Exception ex ) {
log.log( Level.SEVERE, ex.toString(), ex );
}
}
}
}