[concurrency-interest] FooTreeCache

Hanson Char hanson.char at gmail.com
Thu Aug 24 21:01:45 EDT 2006


Hi,

For some reason I need to cache a large but bounded number of of items
in a SortedMap and be able to preload the cache concurrently (ie
without significantly impacting the handling of service requests.)
Actually the closes thing I need is probably ConcurrentSkipListMap in
Java 6, but suffice to say I can't use it yet.  Below is a much
simplified version of such hypothetical cache.

The basic idea is to combine the use of
1) Read/Write Lock;
2) volatile; and
3) AtomicReference

to maximize concurrency.

Can someone spot any significant concurrency issue with it ?

(I know there is ConcurrentHashMap, but let's say for some reason I
need an internal SortedMap and not just Map.)

Many thanks!

Hanson Char

/**
  * A simple concurrent cache that, for some reasons, needs to have a
SortedMap internally.
 */
@ThreadSafe
public class FooTreeCache {
    /** Uses a non-synchronized map for optimal performance both for
read and write. */
    @GuardedBy("rwLock")
    private static volatile SortedMap<String,String> sortedMap = new
TreeMap<String,String>();
    private static final Lock readLock;
    private static final Lock writeLock;
    static {
        ReadWriteLock rwLock = new ReentrantReadWriteLock();
        readLock = rwLock.readLock();
        writeLock = rwLock.writeLock();
    }
    /** Atomic Cache status reference. */
    private static final AtomicReference<Status> status = new
AtomicReference<Status>(Status.INIT);

    private FooTreeCache() {
    }

    /**
     * Cache Status.
     *
     * Valid state transitions:
     * INIT       -> TRANSIENT
     * TRANSIENT  -> PRE_LOADED
     * PRE_LOADED -> TRANSIENT
     * TRANSIENT  -> INIT
     */
    public static enum Status {
        INIT,
        TRANSIENT,
        PRE_LOADED;
    }

    /** Preloads from db. */
    public static void preload() {
        if (!status.compareAndSet(Status.INIT, Status.TRANSIENT))
        {   // already loading or preloaded by another thread.
            return;
        }
        Thread t = new Thread("FooTreeCachePreLoader")
        {
            @Override
            public void run()
            {
                Collection<Map.Entry<String,String>> col = // ...
lenghty IO to load or whatever
                SortedMap<String,String> temp = new TreeMap<String,String>();
                for (Map.Entry<String,String> ccbr : col)
                    temp.put(ccbr.getKey(), ccbr.getValue());
                // Replace the cache with a preloaded one.
                sortedMap = temp;
                status.set(Status.PRE_LOADED);
            }
        };
        t.setDaemon(true);
        t.start();
    }

    public static String get(int key)
    {
        readLock.lock();
        try {
            return sortedMap.get(key);
        } finally {
            readLock.unlock();
        }
    }

    public static void put(String key, String value)
    {
        writeLock.lock();
        try {
            sortedMap.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }

    public static int size() {
        readLock.lock();
        try {
            return sortedMap.size();
        } finally {
            readLock.unlock();
        }
    }

    public static void clear() {
        if (!status.compareAndSet(Status.PRE_LOADED, Status.TRANSIENT))
            if (!status.compareAndSet(Status.INIT, Status.TRANSIENT))
                return;
        SortedMap<String,String> temp = sortedMap;
        // Replace the cache with an empty one.
        sortedMap = new TreeMap<String,String>();
        status.set(Status.INIT);
        // Clear the old one for better garbage collection ?
        temp.clear();
    }
}
// End


More information about the Concurrency-interest mailing list