package ini.trakem2.utils; import java.awt.Image; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; import java.lang.ref.SoftReference; import java.lang.reflect.Array; import java.util.HashMap; import java.util.LinkedList; public class CachingThread extends Thread { static private final int SIZE = 10; static private final class ArrayCache<A> extends HashMap<Integer, LinkedList<SoftReference<A>>> { private static final long serialVersionUID = 1L; private final Class<A> clazz; private int count = 0; private ArrayCache(final Class<A> clazz) { this.clazz = clazz; } private final A getOrCreateArray(final int length) { final LinkedList<SoftReference<A>> l = this.get(length); if (null == l) return newArray(length); if (l.isEmpty()) { this.remove(length); return newArray(length); } // Else: A a; do { a = l.removeFirst().get(); --count; } while (null == a && !l.isEmpty()); return null == a ? newArray(length) : a; } @SuppressWarnings("unchecked") private final A newArray(final int length) { return (A) Array.newInstance(clazz.getComponentType(), length); } private final void storeForReuse(final A a, final int length) { LinkedList<SoftReference<A>> l = this.get(length); if (null == l) { l = new LinkedList<SoftReference<A>>(); this.put(length, l); } l.add(new SoftReference<A>(a)); ++count; // Clean up if (count > SIZE) { restructure(); } } @SuppressWarnings("unchecked") synchronized private final void restructure() { count = 0; final LinkedList<SoftReference<A>>[] ls = this.values().toArray(new LinkedList[this.size()]); for (final LinkedList<SoftReference<A>> l : ls) { final SoftReference<A>[] s = l.toArray(new SoftReference[l.size()]); // Remove stale references and crop to maximum SIZE l.clear(); for (int i=0, c=count; i < s.length && c < SIZE; ++i) { if (null == s[i].get()) continue; // stale reference // Re-add good reference l.add(s[i]); ++c; } // Update count += l.size(); } } } private final ArrayCache<byte[]> cacheBytes = new ArrayCache<byte[]>(byte[].class); private final ArrayCache<int[]> cacheInts = new ArrayCache<int[]>(int[].class); public void clear() { synchronized (cacheBytes) { cacheBytes.clear(); } synchronized (cacheInts) { cacheInts.clear(); } } public CachingThread() { super(); } public CachingThread(final Runnable r) { super(r); } public CachingThread(final String name) { super(name); } public CachingThread(final Runnable r, final String name) { super(r, name); } public CachingThread(final ThreadGroup tg, final Runnable r) { super(tg, r); } public CachingThread(final ThreadGroup tg, final String name) { super(tg, name); } public CachingThread(final ThreadGroup tg, final Runnable r, final String name) { super(tg, r, name); } public CachingThread(final ThreadGroup tg, final Runnable r, final String name, final long priority) { super(tg, r, name, priority); } public static final byte[][] getOrCreateByteArray(final int num, final int length) { final Thread t = Thread.currentThread(); if (CachingThread.class.isAssignableFrom(t.getClass())) { final CachingThread c = (CachingThread) t; final byte[][] b = new byte[num][]; for (int i=0; i<num; ++i) b[i] = c.cacheBytes.getOrCreateArray(length); return b; } return new byte[num][length]; } public static final byte[] getOrCreateByteArray(final int length) { final Thread t = Thread.currentThread(); if (CachingThread.class.isAssignableFrom(t.getClass())) { final CachingThread c = (CachingThread) t; Object o = c.cacheBytes.getOrCreateArray(length); System.out.println("instance of: " + o.getClass()); return (byte[]) o; } return new byte[length]; } public static final int[] getOrCreateIntArray(final int length) { final Thread t = Thread.currentThread(); if (CachingThread.class.isAssignableFrom(t.getClass())) { final CachingThread c = (CachingThread) t; return c.cacheInts.getOrCreateArray(length); } return new int[length]; } public static final void storeForReuse(final byte[][] b) { final Thread t = Thread.currentThread(); if (CachingThread.class.isAssignableFrom(t.getClass())) { final CachingThread c = (CachingThread) t; for (int i=0; i<b.length; ++i) c.cacheBytes.storeForReuse(b[i], b[i].length); } } public static final void storeForReuse(final byte[] b) { final Thread t = Thread.currentThread(); if (CachingThread.class.isAssignableFrom(t.getClass())) { final CachingThread c = (CachingThread) t; c.cacheBytes.storeForReuse(b, b.length); } } public static final void storeForReuse(final int[] b) { final Thread t = Thread.currentThread(); if (CachingThread.class.isAssignableFrom(t.getClass())) { final CachingThread c = (CachingThread) t; c.cacheInts.storeForReuse(b, b.length); } } public static final void storeArrayForReuse(final Image img) { if (img.getClass() == BufferedImage.class) { final DataBuffer db = ((BufferedImage)img).getData().getDataBuffer(); if (db.getClass() == DataBufferInt.class) { CachingThread.storeForReuse(((DataBufferInt)db).getData()); } else if (db.getClass() == DataBufferByte.class) { CachingThread.storeForReuse(((DataBufferByte)db).getData()); } } } /** Tell all instances to clear their caches. */ public static final void releaseAll() { // Find the top-most parent Thread ThreadGroup parent = Thread.currentThread().getThreadGroup(); while (true) { ThreadGroup p = parent.getParent(); if (null == p) break; parent = p; } // Collect all live Thread instances Thread[] ts = new Thread[parent.activeCount()]; while (parent.enumerate(ts, true) == ts.length) { ts = new Thread[ ts.length * 2 ]; } // For each Thread, if it's a CachingThread, clear its contents for (Thread t : ts) { if (null == t) continue; if (CachingThread.class.isAssignableFrom(t.getClass())) { ((CachingThread)t).clear(); } } } }