package edu.stanford.nlp.util.concurrent;
import java.util.Set;
import edu.stanford.nlp.util.Generics;
import edu.stanford.nlp.util.Interner;
/**
* <p>
* For interning (canonicalizing) things in a multi-threaded environment.
* </p>
*
* <p>
* Maps any object to a unique interned version which .equals the
* presented object. If presented with a new object which has no
* previous interned version, the presented object becomes the
* interned version. You can tell if your object has been chosen as
* the new unique representative by checking whether o == intern(o).
* The interners use a concurrent map with weak references, meaning that
* if the only pointers to an interned item are the interners' backing maps,
* that item can still be garbage collected. Since the gc thread can
* silently remove things from the backing map, there's no public way to
* get the backing map, but feel free to add one at your own risk.
* </p>
* Note that in general it is just as good or better to use the
* static SynchronizedInterner.globalIntern() method rather than making an
* instance of SynchronizedInterner and using the instance-level intern().
* <p/>
*
* @author Ilya Sherman
* @see edu.stanford.nlp.util.Interner
*/
// TODO would be nice to have this share an interface with Interner
public class SynchronizedInterner<T> {
protected static final Object globalMutex = new Object();
protected static SynchronizedInterner<Object> interner =
Generics.newSynchronizedInterner(Interner.getGlobal(), globalMutex);
/**
* For getting the instance that global methods use.
*/
public static SynchronizedInterner<Object> getGlobal() {
synchronized(globalMutex) {
return interner;
}
}
/**
* For supplying a new instance for the global methods to use.
*
* @return the previous global interner.
*/
public static SynchronizedInterner<Object> setGlobal(Interner<Object> delegate) {
synchronized(globalMutex) {
SynchronizedInterner<Object> oldInterner = SynchronizedInterner.interner;
SynchronizedInterner.interner = Generics.newSynchronizedInterner(delegate);
return oldInterner;
}
}
/**
* Returns a unique object o' that .equals the argument o. If o
* itself is returned, this is the first request for an object
* .equals to o.
*/
@SuppressWarnings("unchecked")
public static <T> T globalIntern(T o) {
synchronized(globalMutex) {
return (T) getGlobal().intern(o);
}
}
protected final Interner<T> delegate;
protected final Object mutex;
public SynchronizedInterner(Interner<T> delegate) {
if (delegate == null) throw new NullPointerException();
this.delegate = delegate;
this.mutex = this;
}
public SynchronizedInterner(Interner<T> delegate, Object mutex) {
if (delegate == null) throw new NullPointerException();
this.delegate = delegate;
this.mutex = mutex;
}
public void clear() {
synchronized(mutex) {
delegate.clear();
}
}
/**
* Returns a unique object o' that .equals the argument o. If o
* itself is returned, this is the first request for an object
* .equals to o.
*/
public T intern(T o) {
synchronized(mutex) {
return delegate.intern(o);
}
}
/**
* Returns a <code>Set</code> such that each element in the returned set
* is a unique object e' that .equals the corresponding element e in the
* original set.
*/
public Set<T> internAll(Set<T> s) {
synchronized(mutex) {
return delegate.internAll(s);
}
}
public int size() {
synchronized(mutex) {
return delegate.size();
}
}
/**
* Test method: interns its arguments and says whether they == themselves.
* @throws InterruptedException
*/
public static void main(final String[] args) throws InterruptedException {
final Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (String str : args) {
String interned = SynchronizedInterner.globalIntern(str);
Thread.yield();
if (interned != str)
throw new AssertionError("Interning failed for " + str);
}
});
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
}
}