This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jcs.git

commit 593885cb5b97bedb58eccd9f864966ceda77d2c2
Author: Thomas Vandahl <[email protected]>
AuthorDate: Fri Sep 4 22:41:12 2026 +0200

    A bit of reorganization in basic structures
---
 .../jcs4/engine/control/CompositeCache.java        |   7 +-
 .../AbstractDoubleLinkedListMemoryCache.java       | 300 ++++++++-------------
 .../jcs4/engine/memory/AbstractMemoryCache.java    | 255 ++++++++++++------
 .../jcs4/engine/memory/behavior/IMemoryCache.java  |   8 -
 .../jcs4/engine/memory/fifo/FIFOMemoryCache.java   |  17 +-
 .../jcs4/engine/memory/lru/LHMLRUMemoryCache.java  |  92 +++----
 .../jcs4/engine/memory/lru/LRUMemoryCache.java     |  17 +-
 .../jcs4/engine/memory/mru/MRUMemoryCache.java     |  17 +-
 .../memory/soft/SoftReferenceMemoryCache.java      | 139 +++++-----
 .../jcs4/utils/struct/DoubleLinkedList.java        | 170 ++++--------
 .../jcs4/engine/memory/MockMemoryCache.java        |  26 --
 .../engine/memory/mru/MRUMemoryCacheUnitTest.java  |  31 ++-
 12 files changed, 496 insertions(+), 583 deletions(-)

diff --git 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/control/CompositeCache.java
 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/control/CompositeCache.java
index e99b7947..4c471d5d 100644
--- 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/control/CompositeCache.java
+++ 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/control/CompositeCache.java
@@ -185,19 +185,16 @@ public class CompositeCache<K, V>
             {
                 final Class<?> c = Class.forName(cattr.MemoryCacheName());
                 @SuppressWarnings("unchecked") // Need cast
-                final
-                IMemoryCache<K, V> newInstance =
+                final IMemoryCache<K, V> newInstance =
                     (IMemoryCache<K, V>) 
c.getDeclaredConstructor().newInstance();
                 memCache = newInstance;
-                memCache.initialize(this);
             }
             catch (final Exception e)
             {
                 log.warn("Failed to init mem cache, using: LRUMemoryCache", e);
-
                 this.memCache = new LRUMemoryCache<>();
-                this.memCache.initialize(this);
             }
+            memCache.initialize(this);
         }
         else
         {
diff --git 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/AbstractDoubleLinkedListMemoryCache.java
 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/AbstractDoubleLinkedListMemoryCache.java
index 1de4c48b..848db553 100644
--- 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/AbstractDoubleLinkedListMemoryCache.java
+++ 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/AbstractDoubleLinkedListMemoryCache.java
@@ -20,8 +20,8 @@ package org.apache.commons.jcs4.engine.memory;
  */
 
 import java.io.IOException;
+import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
 
 import org.apache.commons.jcs4.engine.behavior.ICacheElement;
 import org.apache.commons.jcs4.engine.control.CompositeCache;
@@ -44,67 +44,47 @@ public abstract class 
AbstractDoubleLinkedListMemoryCache<K, V> extends Abstract
     /** The logger. */
     private static final Log log = 
Log.getLog(AbstractDoubleLinkedListMemoryCache.class);
 
+    static
+    {
+        cacheImplementationName = "Abstract DoubleLinkedList Memory Cache";
+    }
+
     /** Thread-safe double linked list for lru */
     private DoubleLinkedList<MemoryElementDescriptor<K, V>> list;
 
     /**
      * Adds a new node to the start of the link list.
-     * <p>
+     * (guarded by the lock)
      *
-     * @param ce
-     *            The feature to be added to the First
-     * @return MemoryElementDescriptor
+     * @param me The MemoryElementDescriptor to be added to the start of the 
list
      */
-    protected MemoryElementDescriptor<K, V> addFirst(final ICacheElement<K, V> 
ce)
+    protected void addFirst(final MemoryElementDescriptor<K, V> me)
     {
-        lock.lock();
-        try
+        list.addFirst(me);
+        if ( log.isTraceEnabled() )
         {
-            final MemoryElementDescriptor<K, V> me = new 
MemoryElementDescriptor<>(ce);
-            list.addFirst(me);
-            if ( log.isTraceEnabled() )
-            {
-                verifyCache(ce.key());
-            }
-            return me;
-        }
-        finally
-        {
-            lock.unlock();
+            verifyCache(me.getCacheElement().key());
         }
     }
 
     /**
      * Adds a new node to the end of the link list.
-     * <p>
+     * (guarded by the lock)
      *
-     * @param ce
-     *            The feature to be added to the First
-     * @return MemoryElementDescriptor
+     * @param me The feature to be added to the end of the list
      */
-    protected MemoryElementDescriptor<K, V> addLast(final ICacheElement<K, V> 
ce)
+    protected void addLast(final MemoryElementDescriptor<K,V> me)
     {
-        lock.lock();
-        try
-        {
-            final MemoryElementDescriptor<K, V> me = new 
MemoryElementDescriptor<>(ce);
-            list.addLast(me);
-            if ( log.isTraceEnabled() )
-            {
-                verifyCache(ce.key());
-            }
-            return me;
-        }
-        finally
+        list.addLast(me);
+        if ( log.isTraceEnabled() )
         {
-            lock.unlock();
+            verifyCache(me.getCacheElement().key());
         }
     }
 
     /**
      * Adjust the list as needed for a get. This allows children to control 
the algorithm
      * (guarded by the lock)
-     * <p>
      *
      * @param list the node list
      * @param me the current cache element
@@ -115,105 +95,40 @@ public abstract class 
AbstractDoubleLinkedListMemoryCache<K, V> extends Abstract
      * Children implement this to control the cache expiration algorithm
      * <p>
      *
-     * @param ce the current cache element
-     * @return MemoryElementDescriptor the new node
-     * @throws IOException
+     * @param me the current cache element
      */
-    protected abstract MemoryElementDescriptor<K, V> 
adjustListForUpdate(ICacheElement<K, V> ce) throws IOException;
+    protected abstract void adjustListForUpdate(MemoryElementDescriptor<K, V> 
me);
 
     /**
      * This is called by super initialize.
      *
-     * NOTE: should return a thread safe map
-     *
-     * <p>
-     *
-     * @return new ConcurrentHashMap()
+     * @return new HashMap()
      */
     @Override
-    public ConcurrentMap<K, MemoryElementDescriptor<K, V>> createMap()
+    protected Map<K, MemoryElementDescriptor<K, V>> createMap()
     {
         return new ConcurrentHashMap<>();
     }
 
-    /**
-     * Dump the cache entries from first to list for debugging.
-     */
-    private void dumpCacheEntries()
-    {
-        log.trace("dumpingCacheEntries");
-        for (MemoryElementDescriptor<K, V> me = list.getFirst(); me != null; 
me = (MemoryElementDescriptor<K, V>) me.next)
-        {
-            log.trace("dumpCacheEntries> key={0}, val={1}",
-                    me.getCacheElement().key(), me.getCacheElement().value());
-        }
-    }
-
-    /**
-     * This instructs the memory cache to remove the <em>numberToFree</em> 
according to its eviction
-     * policy. For example, the LRUMemoryCache will remove the 
<em>numberToFree</em> least recently
-     * used items. These will be spooled to disk if a disk auxiliary is 
available.
-     * <p>
-     *
-     * @param numberToFree
-     * @return The number that were removed. if you ask to free 5, but there 
are only 3, you will
-     *         get 3.
-     */
-    @Override
-    public int freeElements(final int numberToFree)
-    {
-        int freed = 0;
-
-        lock.lock();
-
-        try
-        {
-            for (; freed < numberToFree; freed++)
-            {
-                final ICacheElement<K, V> element = spoolLastElement();
-                if (element == null)
-                {
-                    break;
-                }
-            }
-        }
-        finally
-        {
-            lock.unlock();
-        }
-
-        return freed;
-    }
-
     /**
      * @see 
org.apache.commons.jcs4.engine.memory.AbstractMemoryCache#get(Object)
      */
     @Override
     public ICacheElement<K, V> get(final K key) throws IOException
     {
-        lock.lock();
-
-        try
-        {
-            final ICacheElement<K, V> ce = super.get(key);
-
-            if (log.isTraceEnabled())
-            {
-                verifyCache();
-            }
+        final ICacheElement<K, V> ce = super.get(key);
 
-            return ce;
-        }
-        finally
+        if (log.isTraceEnabled())
         {
-            lock.unlock();
+            verifyCache();
         }
+
+        return ce;
     }
 
     /**
      * This returns semi-structured information on the memory cache, such as 
the size, put count,
      * hit count, and miss count.
-     * <p>
      *
      * @see 
org.apache.commons.jcs4.engine.memory.behavior.IMemoryCache#getStatistics()
      */
@@ -221,7 +136,6 @@ public abstract class 
AbstractDoubleLinkedListMemoryCache<K, V> extends Abstract
     public IStats getStatistics()
     {
         final IStats stats = super.getStatistics();
-        stats.setTypeName( /* add algorithm name */"Memory Cache");
         stats.addStatElement("List Size", Integer.valueOf(list.size()));
 
         return stats;
@@ -241,6 +155,18 @@ public abstract class 
AbstractDoubleLinkedListMemoryCache<K, V> extends Abstract
         log.info("initialized MemoryCache for {0}", this::getCacheName);
     }
 
+    /**
+     * Wrap the cache element into an appropriate memory element descriptor
+     *
+     * @param ce The cache element
+     * @return The memory element descriptor
+     */
+    @Override
+    protected MemoryElementDescriptor<K, V> wrap(ICacheElement<K, V> ce)
+    {
+        return new MemoryElementDescriptor<>(ce);
+    }
+
     /**
      * Update control structures after get
      * (guarded by the lock)
@@ -253,6 +179,30 @@ public abstract class 
AbstractDoubleLinkedListMemoryCache<K, V> extends Abstract
         adjustListForGet(list, me);
     }
 
+    /**
+     * Update control structures after update
+     * (guarded by the lock)
+     *
+     * @param newNode The memory element descriptor of the current cache 
element
+     * @param oldNode The memory element descriptor of the previous cache 
element
+     * @throws IOException if spooling operation fails
+     */
+    @Override
+    protected void lockedUpdateElement(MemoryElementDescriptor<K, V> newNode,
+            MemoryElementDescriptor<K, V> oldNode) throws IOException
+    {
+        adjustListForUpdate(newNode);
+
+        // If the node was the same as an existing node, remove it.
+        if (oldNode != null && 
newNode.getCacheElement().key().equals(oldNode.getCacheElement().key()))
+        {
+            list.remove(oldNode);
+        }
+
+        // If we are over the max spool some
+        spoolIfNeeded();
+    }
+
     /**
      * Removes all cached items from the cache control structures.
      * (guarded by the lock)
@@ -275,62 +225,75 @@ public abstract class 
AbstractDoubleLinkedListMemoryCache<K, V> extends Abstract
         list.remove(me);
     }
 
+    /**
+     * This instructs the memory cache to remove the <em>numberToFree</em> 
according to its eviction
+     * policy. For example, the LRUMemoryCache will remove the 
<em>numberToFree</em> least recently
+     * used items. These will be spooled to disk if a disk auxiliary is 
available.
+     * (guarded by the lock)
+     *
+     * @param numberToFree
+     * @return The number that were removed. if you ask to free 5, but there 
are only 3, you will
+     *         get 3.
+     */
+    @Override
+    protected int lockedFreeElements(final int numberToFree) throws IOException
+    {
+        int freed = 0;
+
+        for (; freed < numberToFree; freed++)
+        {
+            final ICacheElement<K, V> element = spoolLastElement();
+            if (element == null)
+            {
+                break;
+            }
+        }
+
+        return freed;
+    }
+
     /**
      * If the max size has been reached, spool.
-     * <p>
+     * (guarded by the lock)
      *
-     * @throws Error
+     * @throws IOException
      */
-    private void spoolIfNeeded() throws Error
+    private void spoolIfNeeded() throws IOException
     {
         // The spool will put them in a disk event queue, so there is no
         // need to pre-queue the queuing. This would be a bit wasteful
         // and wouldn't save much time in this synchronous call.
-        lock.lock();
-
-        try
+        final int size = getSize();
+        // If the element limit is reached, we need to spool
+        if (size <= getCacheAttributes().MaxObjects())
         {
-            final int size = map.size();
-            // If the element limit is reached, we need to spool
-
-            if (size <= getCacheAttributes().MaxObjects())
-            {
-                return;
-            }
+            return;
+        }
 
-            log.debug("In memory limit reached, spooling");
+        log.debug("In memory limit reached, spooling");
 
-            // Write the last 'chunkSize' items to disk.
-            final int chunkSizeCorrected = Math.min(size, chunkSize);
+        // Write the last 'chunkSize' items to disk.
+        final int chunkSizeCorrected = Math.min(size, 
getCacheAttributes().SpoolChunkSize());
 
-            log.debug("About to spool to disk cache, map size: {0}, max 
objects: {1}, "
-                    + "maximum items to spool: {2}", () -> size,
-                    getCacheAttributes()::MaxObjects,
-                    () -> chunkSizeCorrected);
+        log.debug("About to spool to disk cache, map size: {0}, max objects: 
{1}, "
+                + "maximum items to spool: {2}", () -> size,
+                getCacheAttributes()::MaxObjects,
+                () -> chunkSizeCorrected);
 
-            freeElements(chunkSizeCorrected);
+        freeElements(chunkSizeCorrected);
 
-            // If this is out of the sync block it can detect a mismatch
-            // where there is none.
-            if (log.isDebugEnabled() && map.size() != list.size())
-            {
-                log.debug("update: After spool, size mismatch: map.size() = 
{0}, "
-                        + "linked list size = {1}", map.size(), list.size());
-            }
-        }
-        finally
+        // If this is out of the sync block it can detect a mismatch
+        // where there is none.
+        if (log.isDebugEnabled() && getSize() != list.size())
         {
-            lock.unlock();
+            log.debug("update: After spool, size mismatch: map.size() = {0}, "
+                    + "linked list size = {1}", getSize(), list.size());
         }
-
-        log.debug("update: After spool map size: {0} linked list size = {1}",
-                () -> map.size(), () -> list.size());
     }
 
     /**
      * This spools the last element in the LRU, if one exists.
-     * The method is called guarded by the lock
-     * <p>
+     * (guarded by the lock)
      *
      * @return ICacheElement&lt;K, V&gt; if there was a last element, else 
null.
      * @throws Error
@@ -365,41 +328,16 @@ public abstract class 
AbstractDoubleLinkedListMemoryCache<K, V> extends Abstract
     }
 
     /**
-     * Calls the abstract method updateList.
-     * <p>
-     * If the max size is reached, an element will be put to disk.
-     * <p>
-     *
-     * @param ce
-     *            The cache element, or entry wrapper
-     * @throws IOException
+     * Dump the cache entries from first to list for debugging.
      */
-    @Override
-    public final void update(final ICacheElement<K, V> ce) throws IOException
+    private void dumpCacheEntries()
     {
-        lock.lock();
-        try
-        {
-            super.update(ce);
-            final MemoryElementDescriptor<K, V> newNode = 
adjustListForUpdate(ce);
-
-            // this should be synchronized if we were not using a 
ConcurrentHashMap
-            final K key = newNode.getCacheElement().key();
-            final MemoryElementDescriptor<K, V> oldNode = map.put(key, 
newNode);
-
-            // If the node was the same as an existing node, remove it.
-            if (oldNode != null && key.equals(oldNode.getCacheElement().key()))
-            {
-                list.remove(oldNode);
-            }
-        }
-        finally
+        log.trace("dumpingCacheEntries");
+        for (MemoryElementDescriptor<K, V> me = list.getFirst(); me != null; 
me = (MemoryElementDescriptor<K, V>) me.next)
         {
-            lock.unlock();
+            log.trace("dumpCacheEntries> key={0}, val={1}",
+                    me.getCacheElement().key(), me.getCacheElement().value());
         }
-
-        // If we are over the max spool some
-        spoolIfNeeded();
     }
 
     /**
@@ -410,7 +348,7 @@ public abstract class 
AbstractDoubleLinkedListMemoryCache<K, V> extends Abstract
     {
         boolean found = false;
         log.trace("verifycache[{0}]: map contains {1} elements, linked list "
-                + "contains {2} elements", getCacheName(), map.size(),
+                + "contains {2} elements", getCacheName(), getSize(),
                 list.size());
         log.trace("verifycache: checking linked list by key ");
         for (MemoryElementDescriptor<K, V> li = list.getFirst(); li != null; 
li = (MemoryElementDescriptor<K, V>) li.next)
diff --git 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/AbstractMemoryCache.java
 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/AbstractMemoryCache.java
index 2a34cbaf..1421f5c3 100644
--- 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/AbstractMemoryCache.java
+++ 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/AbstractMemoryCache.java
@@ -20,14 +20,14 @@ package org.apache.commons.jcs4.engine.memory;
  */
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.HashMap;
-import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -52,6 +52,9 @@ public abstract class AbstractMemoryCache<K, V>
     /** Log instance */
     private static final Log log = Log.getLog( AbstractMemoryCache.class );
 
+    /** The cache implementation name */
+    protected static String cacheImplementationName = "Abstract Memory Cache";
+
     /** Cache Attributes.  Regions settings. */
     private ICompositeCacheAttributes cacheAttributes;
 
@@ -61,10 +64,8 @@ public abstract class AbstractMemoryCache<K, V>
     /** The cache region name this store is associated with */
     private String cacheName;
 
-    /** How many to spool at a time. */
-    protected int chunkSize;
-
-    protected final Lock lock = new ReentrantLock();
+    /** The lock */
+    protected final ReadWriteLock lock = new ReentrantReadWriteLock();
 
     /** Map where items are stored by key.  This is created by the concrete 
child class. */
     protected Map<K, MemoryElementDescriptor<K, V>> map; // TODO privatise
@@ -80,11 +81,47 @@ public abstract class AbstractMemoryCache<K, V>
 
     /**
      * Children must implement this method. A FIFO implementation may use a 
tree map. An LRU might
-     * use a hashtable. The map returned should be threadsafe.
+     * use a hashtable.
      *
-     * @return A threadsafe Map
+     * @return A Map
      */
-    public abstract Map<K, MemoryElementDescriptor<K, V>> createMap();
+    protected abstract Map<K, MemoryElementDescriptor<K, V>> createMap();
+
+    /**
+     * Get a read-only map view
+     * @return a read-only map view
+     */
+    protected Map<K, MemoryElementDescriptor<K, V>> getMapView()
+    {
+        return Collections.unmodifiableMap(map);
+    }
+
+    /**
+     * This instructs the memory cache to remove the <em>numberToFree</em> 
according to its eviction
+     * policy. For example, the LRUMemoryCache will remove the 
<em>numberToFree</em> least recently
+     * used items. These will be spooled to disk if a disk auxiliary is 
available.
+     *
+     * @param numberToFree
+     * @return The number that were removed. if you ask to free 5, but there 
are only 3, you will
+     *         get 3.
+     */
+    @Override
+    public int freeElements(final int numberToFree) throws IOException
+    {
+        int freed = 0;
+
+        lock.writeLock().lock();
+        try
+        {
+            freed = lockedFreeElements(numberToFree);
+        }
+        finally
+        {
+            lock.writeLock().unlock();
+        }
+
+        return freed;
+    }
 
     /**
      * Prepares for shutdown. Reset statistics
@@ -130,32 +167,31 @@ public abstract class AbstractMemoryCache<K, V>
         log.debug("{0}: getting item for key {1}", this::getCacheName,
                 () -> key);
 
-        final MemoryElementDescriptor<K, V> me = map.get(key);
-
-        if (me != null)
+        lock.writeLock().lock();
+        try
         {
-            hitCnt.incrementAndGet();
-            ce = me.getCacheElement();
+            final MemoryElementDescriptor<K, V> me = map.get(key);
 
-            lock.lock();
-            try
+            if (me != null)
             {
+                hitCnt.incrementAndGet();
                 lockedGetElement(me);
+                ce = me.getCacheElement();
+
+                log.debug("{0}: MemoryCache hit for {1}", this::getCacheName,
+                        () -> key);
             }
-            finally
+            else
             {
-                lock.unlock();
-            }
+                missCnt.incrementAndGet();
 
-            log.debug("{0}: MemoryCache hit for {1}", this::getCacheName,
-                    () -> key);
+                log.debug("{0}: MemoryCache miss for {1}", this::getCacheName,
+                        () -> key);
+            }
         }
-        else
+        finally
         {
-            missCnt.incrementAndGet();
-
-            log.debug("{0}: MemoryCache miss for {1}", this::getCacheName,
-                    () -> key);
+            lock.writeLock().unlock();
         }
 
         return ce;
@@ -190,7 +226,15 @@ public abstract class AbstractMemoryCache<K, V>
     @Override
     public Set<K> getKeySet()
     {
-        return new LinkedHashSet<>(map.keySet());
+        lock.readLock().lock();
+        try
+        {
+            return Collections.unmodifiableSet(map.keySet());
+        }
+        finally
+        {
+            lock.readLock().unlock();
+        }
     }
 
     /**
@@ -266,7 +310,15 @@ public abstract class AbstractMemoryCache<K, V>
     @Override
     public int getSize()
     {
-        return this.map.size();
+        lock.readLock().lock();
+        try
+        {
+            return this.map.size();
+        }
+        finally
+        {
+            lock.readLock().unlock();
+        }
     }
 
     /**
@@ -275,7 +327,7 @@ public abstract class AbstractMemoryCache<K, V>
     @Override
     public IStats getStatistics()
     {
-        final IStats stats = new Stats("Abstract Memory Cache");
+        final IStats stats = new Stats(cacheImplementationName);
 
         stats.addStatElement("Put Count", putCnt);
         stats.addStatElement("Hit Count", hitCnt);
@@ -298,13 +350,22 @@ public abstract class AbstractMemoryCache<K, V>
         putCnt = new AtomicLong();
 
         this.cacheAttributes = hub.getCacheAttributes();
-        this.chunkSize = cacheAttributes.SpoolChunkSize();
         final String attributeCacheName = this.cacheAttributes.cacheName();
         this.cacheName = attributeCacheName == null ? hub.getCacheName() : 
attributeCacheName;
         this.waterfall = ce -> hub.spoolToDisk(ce);
         this.map = createMap();
+
+        log.info("initialized {0} for {1}", cacheImplementationName, 
cacheName);
     }
 
+    /**
+     * Wrap the cache element into an appropriate memory element descriptor
+     *
+     * @param ce The cache element
+     * @return The memory element descriptor
+     */
+    protected abstract MemoryElementDescriptor<K, V> wrap(ICacheElement<K, V> 
ce);
+
     /**
      * Update control structures after get
      * (guarded by the lock)
@@ -313,6 +374,17 @@ public abstract class AbstractMemoryCache<K, V>
      */
     protected abstract void lockedGetElement(MemoryElementDescriptor<K, V> me);
 
+    /**
+     * Update control structures after update
+     * (guarded by the lock)
+     *
+     * @param newNode The memory element descriptor of the current cache 
element
+     * @param oldNode The memory element descriptor of the previous cache 
element
+     * @throws IOException if spooling operation fails
+     */
+    protected abstract void lockedUpdateElement(MemoryElementDescriptor<K, V> 
newNode,
+            MemoryElementDescriptor<K, V> oldNode) throws IOException;
+
     /**
      * Removes all cached items from the cache control structures.
      * (guarded by the lock)
@@ -327,6 +399,18 @@ public abstract class AbstractMemoryCache<K, V>
      */
     protected abstract void lockedRemoveElement(MemoryElementDescriptor<K, V> 
me);
 
+    /**
+     * This instructs the memory cache to remove the <em>numberToFree</em> 
according to its eviction
+     * policy. For example, the LRUMemoryCache will remove the 
<em>numberToFree</em> least recently
+     * used items. These will be spooled to disk if a disk auxiliary is 
available.
+     * (guarded by the lock)
+     *
+     * @param numberToFree
+     * @return The number that were removed. if you ask to free 5, but there 
are only 3, you will
+     *         get 3.
+     */
+    protected abstract int lockedFreeElements(final int numberToFree) throws 
IOException;
+
     /**
      * Removes an item from the cache. This method handles hierarchical 
removal. If the key is a
      * String and ends with the CacheConstants.NAME_COMPONENT_DELIMITER, then 
all items with keys
@@ -347,16 +431,16 @@ public abstract class AbstractMemoryCache<K, V>
         // handle partial removal
         if (key instanceof String s && 
s.endsWith(ICache.NAME_COMPONENT_DELIMITER))
         {
-            removed = removeByHierarchy(key);
+            removed = removeByHierarchy(s);
         }
         else if (key instanceof GroupAttrName gan && gan.attrName() == null)
         {
-            removed = removeByGroup(key);
+            removed = removeByGroup(gan.groupId());
         }
         else
         {
             // remove single item.
-            lock.lock();
+            lock.writeLock().lock();
             try
             {
                 final MemoryElementDescriptor<K, V> me = map.remove(key);
@@ -368,7 +452,7 @@ public abstract class AbstractMemoryCache<K, V>
             }
             finally
             {
-                lock.unlock();
+                lock.writeLock().unlock();
             }
         }
 
@@ -383,90 +467,75 @@ public abstract class AbstractMemoryCache<K, V>
     @Override
     public void removeAll() throws IOException
     {
-        lock.lock();
+        lock.writeLock().lock();
         try
         {
-            lockedRemoveAll();
             map.clear();
+            lockedRemoveAll();
         }
         finally
         {
-            lock.unlock();
+            lock.writeLock().unlock();
         }
     }
 
     /**
      * Remove all keys of the same group hierarchy.
-     * @param key The key
+     * @param groupId The group attribute id
      * @return true if something has been removed
      */
-    protected boolean removeByGroup(final K key)
+    protected boolean removeByGroup(final GroupId groupId)
     {
-        final GroupId groupId = ((GroupAttrName<?>) key).groupId();
-
-        // remove all keys of the same group hierarchy.
-        return map.entrySet().removeIf(entry -> {
-            final K k = entry.getKey();
+        lock.writeLock().lock();
+        try
+        {
+            // remove all keys of the same group hierarchy.
+            return map.entrySet().removeIf(entry -> {
+                final K k = entry.getKey();
 
-            if (k instanceof GroupAttrName gan && 
gan.groupId().equals(groupId))
-            {
-                lock.lock();
-                try
-                {
-                    lockedRemoveElement(entry.getValue());
-                    return true;
-                }
-                finally
+                if (k instanceof GroupAttrName kgan && 
kgan.groupId().equals(groupId))
                 {
-                    lock.unlock();
+                        lockedRemoveElement(entry.getValue());
+                        return true;
                 }
-            }
 
-            return false;
-        });
+                return false;
+            });
+        }
+        finally
+        {
+            lock.writeLock().unlock();
+        }
     }
 
     /**
      * Remove all keys of the same name hierarchy.
      *
-     * @param key The key
+     * @param keyString The key as string
      * @return true if something has been removed
      */
-    protected boolean removeByHierarchy(final K key)
+    protected boolean removeByHierarchy(final String keyString)
     {
-        final String keyString = key.toString();
-
-        // remove all keys of the same name hierarchy.
-        return map.entrySet().removeIf(entry -> {
-            final K k = entry.getKey();
+        lock.writeLock().lock();
+        try
+        {
+            // remove all keys of the same name hierarchy.
+            return map.entrySet().removeIf(entry -> {
+                final K k = entry.getKey();
 
-            if (k instanceof String s && s.startsWith(keyString))
-            {
-                lock.lock();
-                try
+                if (k instanceof String s && s.startsWith(keyString))
                 {
                     lockedRemoveElement(entry.getValue());
                     return true;
                 }
-                finally
-                {
-                    lock.unlock();
-                }
-            }
 
-            return false;
-        });
-    }
-
-    /**
-     * Sets the CacheAttributes.
-     *
-     * @param cattr The new CacheAttributes value
-     */
-    @Override
-    public void setCacheAttributes( final ICompositeCacheAttributes cattr )
-    {
-        this.cacheAttributes = cattr;
+                return false;
+            });
+        }
+        finally
+        {
+            lock.writeLock().unlock();
+        }
     }
 
     /**
@@ -480,6 +549,18 @@ public abstract class AbstractMemoryCache<K, V>
         throws IOException
     {
         putCnt.incrementAndGet();
+        final MemoryElementDescriptor<K, V> newNode = wrap(ce);
+
+        lock.writeLock().lock();
+        try
+        {
+            final MemoryElementDescriptor<K, V> oldNode = map.put(ce.key(), 
newNode);
+            lockedUpdateElement(newNode, oldNode);
+        }
+        finally
+        {
+            lock.writeLock().unlock();
+        }
     }
 
     /**
diff --git 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/behavior/IMemoryCache.java
 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/behavior/IMemoryCache.java
index e5d31f92..fd51976e 100644
--- 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/behavior/IMemoryCache.java
+++ 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/behavior/IMemoryCache.java
@@ -148,14 +148,6 @@ public interface IMemoryCache<K, V>
     void removeAll()
         throws IOException;
 
-    /**
-     * Sets the CacheAttributes of the region.
-     *
-     * @param cattr
-     *            The new cacheAttributes value
-     */
-    void setCacheAttributes( ICompositeCacheAttributes cattr );
-
     /**
      * Puts an item to the cache.
      *
diff --git 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/fifo/FIFOMemoryCache.java
 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/fifo/FIFOMemoryCache.java
index 0d188461..07e556eb 100644
--- 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/fifo/FIFOMemoryCache.java
+++ 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/fifo/FIFOMemoryCache.java
@@ -19,9 +19,6 @@ package org.apache.commons.jcs4.engine.memory.fifo;
  * under the License.
  */
 
-import java.io.IOException;
-
-import org.apache.commons.jcs4.engine.behavior.ICacheElement;
 import 
org.apache.commons.jcs4.engine.memory.AbstractDoubleLinkedListMemoryCache;
 import org.apache.commons.jcs4.engine.memory.util.MemoryElementDescriptor;
 import org.apache.commons.jcs4.utils.struct.DoubleLinkedList;
@@ -32,6 +29,11 @@ import org.apache.commons.jcs4.utils.struct.DoubleLinkedList;
 public class FIFOMemoryCache<K, V>
     extends AbstractDoubleLinkedListMemoryCache<K, V>
 {
+    static
+    {
+        cacheImplementationName = "FIFO Memory Cache";
+    }
+
     /**
      * Does nothing.
      *
@@ -47,14 +49,11 @@ public class FIFOMemoryCache<K, V>
      * Puts an item to the cache. Removes any pre-existing entries of the same 
key from the linked
      * list and adds this one first.
      *
-     * @param ce The cache element, or entry wrapper
-     * @return MemoryElementDescriptor the new node
-     * @throws IOException
+     * @param me The cache element, or entry wrapper
      */
     @Override
-    protected MemoryElementDescriptor<K, V> adjustListForUpdate( final 
ICacheElement<K, V> ce )
-        throws IOException
+    protected void adjustListForUpdate(final MemoryElementDescriptor<K, V> me)
     {
-        return addFirst( ce );
+        addFirst(me);
     }
 }
diff --git 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/lru/LHMLRUMemoryCache.java
 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/lru/LHMLRUMemoryCache.java
index 68f6aa8e..d00e8153 100644
--- 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/lru/LHMLRUMemoryCache.java
+++ 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/lru/LHMLRUMemoryCache.java
@@ -20,15 +20,12 @@ package org.apache.commons.jcs4.engine.memory.lru;
  */
 
 import java.io.IOException;
-import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
 import org.apache.commons.jcs4.engine.behavior.ICacheElement;
-import org.apache.commons.jcs4.engine.control.CompositeCache;
 import org.apache.commons.jcs4.engine.memory.AbstractMemoryCache;
 import org.apache.commons.jcs4.engine.memory.util.MemoryElementDescriptor;
-import org.apache.commons.jcs4.engine.stats.behavior.IStats;
 import org.apache.commons.jcs4.log.Log;
 
 /**
@@ -37,6 +34,14 @@ import org.apache.commons.jcs4.log.Log;
 public class LHMLRUMemoryCache<K, V>
     extends AbstractMemoryCache<K, V>
 {
+    /** The Logger. */
+    private static final Log log = Log.getLog( LRUMemoryCache.class );
+
+    static
+    {
+        cacheImplementationName = "LHMLRU Memory Cache";
+    }
+
     /**
      * Implements removeEldestEntry from {@link LinkedHashMap}.
      */
@@ -60,7 +65,6 @@ public class LHMLRUMemoryCache<K, V>
          * @param eldest
          * @return true if removed
          */
-        @SuppressWarnings("synthetic-access")
         @Override
         protected boolean removeEldestEntry( final Map.Entry<K, 
MemoryElementDescriptor<K, V>> eldest )
         {
@@ -73,85 +77,58 @@ public class LHMLRUMemoryCache<K, V>
             log.debug( "LHMLRU max size: {0}. Spooling element, key: {1}",
                     () -> getCacheAttributes().MaxObjects(), element::key);
 
-            waterfall( element );
+            waterfall(element);
 
-            log.debug( "LHMLRU size: {0}", map::size );
+            log.debug("LHMLRU size: {0}", getSize());
             return true;
         }
     }
 
-    /** The Logger. */
-    private static final Log log = Log.getLog( LRUMemoryCache.class );
-
     /**
-     * Returns a synchronized LHMSpooler
+     * Returns a LHMSpooler
      *
-     * @return Collections.synchronizedMap( new LHMSpooler() )
+     * @return new LHMSpooler()
      */
     @Override
-    public Map<K, MemoryElementDescriptor<K, V>> createMap()
-    {
-        return Collections.synchronizedMap( new LHMSpooler() );
-    }
-
-    /**
-     * Dump the cache entries from first to last for debugging.
-     */
-    public void dumpCacheEntries()
+    protected Map<K, MemoryElementDescriptor<K, V>> createMap()
     {
-        dumpMap();
+        return new LHMSpooler();
     }
 
     /**
-     * This can't be implemented.
+     * Wrap the cache element into an appropriate memory element descriptor
      *
-     * @param numberToFree
-     * @return 0
-     * @throws IOException
+     * @param ce The cache element
+     * @return The memory element descriptor
      */
     @Override
-    public int freeElements( final int numberToFree )
-        throws IOException
+    protected MemoryElementDescriptor<K, V> wrap(ICacheElement<K, V> ce)
     {
-        // can't be implemented using the LHM
-        return 0;
+        return new MemoryElementDescriptor<>(ce);
     }
 
     /**
-     * This returns semi-structured information on the memory cache, such as 
the size, put count,
-     * hit count, and miss count.
-     *
-     * @return IStats
-     */
-    @Override
-    public IStats getStatistics()
-    {
-        final IStats stats = super.getStatistics();
-        stats.setTypeName( "LHMLRU Memory Cache" );
-
-        return stats;
-    }
-
-    /**
-     * For post reflection creation initialization
+     * Update control structures after get
+     * (guarded by the lock)
      *
-     * @param hub
+     * @param me The memory element descriptor
      */
     @Override
-    public void initialize( final CompositeCache<K, V> hub )
+    protected void lockedGetElement(final MemoryElementDescriptor<K, V> me)
     {
-        super.initialize( hub );
-        log.info( "initialized LHMLRUMemoryCache for {0}", this::getCacheName 
);
+        // empty
     }
 
     /**
-     * Update control structures after get
+     * Update control structures after update
      * (guarded by the lock)
      *
-     * @param me The memory element descriptor
+     * @param newNode The memory element descriptor of the current cache 
element
+     * @param oldNode The memory element descriptor of the previous cache 
element
      */
     @Override
-    protected void lockedGetElement(final MemoryElementDescriptor<K, V> me)
+    protected void lockedUpdateElement(MemoryElementDescriptor<K, V> newNode,
+            MemoryElementDescriptor<K, V> oldNode)
     {
         // empty
     }
@@ -179,16 +156,15 @@ public class LHMLRUMemoryCache<K, V>
     }
 
     /**
-     * Puts an item to the cache.
+     * This can't be implemented.
      *
-     * @param ce Description of the Parameter
+     * @param numberToFree
+     * @return 0
      * @throws IOException
      */
     @Override
-    public void update( final ICacheElement<K, V> ce )
-        throws IOException
+    protected int lockedFreeElements(final int numberToFree) throws IOException
     {
-        super.update(ce);
-        map.put( ce.key(), new MemoryElementDescriptor<>(ce) );
+        return 0;
     }
 }
diff --git 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/lru/LRUMemoryCache.java
 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/lru/LRUMemoryCache.java
index 2c00917f..6bc6cf39 100644
--- 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/lru/LRUMemoryCache.java
+++ 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/lru/LRUMemoryCache.java
@@ -19,9 +19,6 @@ package org.apache.commons.jcs4.engine.memory.lru;
  * under the License.
  */
 
-import java.io.IOException;
-
-import org.apache.commons.jcs4.engine.behavior.ICacheElement;
 import 
org.apache.commons.jcs4.engine.memory.AbstractDoubleLinkedListMemoryCache;
 import org.apache.commons.jcs4.engine.memory.util.MemoryElementDescriptor;
 import org.apache.commons.jcs4.utils.struct.DoubleLinkedList;
@@ -40,6 +37,11 @@ import org.apache.commons.jcs4.utils.struct.DoubleLinkedList;
 public class LRUMemoryCache<K, V>
     extends AbstractDoubleLinkedListMemoryCache<K, V>
 {
+    static
+    {
+        cacheImplementationName = "LRU Memory Cache";
+    }
+
     /**
      * Makes the item the first in the list.
      *
@@ -55,14 +57,11 @@ public class LRUMemoryCache<K, V>
      * Puts an item to the cache. Removes any pre-existing entries of the same 
key from the linked
      * list and adds this one first.
      *
-     * @param ce The cache element, or entry wrapper
-     * @return MemoryElementDescriptor the new node
-     * @throws IOException
+     * @param me The cache element, or entry wrapper
      */
     @Override
-    protected MemoryElementDescriptor<K, V> adjustListForUpdate( final 
ICacheElement<K, V> ce )
-        throws IOException
+    protected void adjustListForUpdate(final MemoryElementDescriptor<K, V> me)
     {
-        return addFirst( ce );
+        addFirst(me);
     }
 }
diff --git 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/mru/MRUMemoryCache.java
 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/mru/MRUMemoryCache.java
index e0a252bd..a6eca64a 100644
--- 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/mru/MRUMemoryCache.java
+++ 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/mru/MRUMemoryCache.java
@@ -19,9 +19,6 @@ package org.apache.commons.jcs4.engine.memory.mru;
  * under the License.
  */
 
-import java.io.IOException;
-
-import org.apache.commons.jcs4.engine.behavior.ICacheElement;
 import 
org.apache.commons.jcs4.engine.memory.AbstractDoubleLinkedListMemoryCache;
 import org.apache.commons.jcs4.engine.memory.util.MemoryElementDescriptor;
 import org.apache.commons.jcs4.utils.struct.DoubleLinkedList;
@@ -33,6 +30,11 @@ import org.apache.commons.jcs4.utils.struct.DoubleLinkedList;
 public class MRUMemoryCache<K, V>
     extends AbstractDoubleLinkedListMemoryCache<K, V>
 {
+    static
+    {
+        cacheImplementationName = "MRU Memory Cache";
+    }
+
     /**
      * Makes the item the last in the list.
      *
@@ -50,14 +52,11 @@ public class MRUMemoryCache<K, V>
      * It's not clear if the put operation should be different. Perhaps this 
should remove the oldest
      * if full, and then put.
      *
-     * @param ce
-     * @return MemoryElementDescriptor the new node
-     * @throws IOException
+     * @param me The cache element, or entry wrapper
      */
     @Override
-    protected MemoryElementDescriptor<K, V> adjustListForUpdate( final 
ICacheElement<K, V> ce )
-        throws IOException
+    protected void adjustListForUpdate(final MemoryElementDescriptor<K, V> me)
     {
-        return addFirst( ce );
+        addFirst(me);
     }
 }
diff --git 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/soft/SoftReferenceMemoryCache.java
 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/soft/SoftReferenceMemoryCache.java
index f2828723..22c32296 100644
--- 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/soft/SoftReferenceMemoryCache.java
+++ 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/engine/memory/soft/SoftReferenceMemoryCache.java
@@ -21,12 +21,11 @@ package org.apache.commons.jcs4.engine.memory.soft;
 
 import java.io.IOException;
 import java.lang.ref.SoftReference;
-import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.stream.Collectors;
 
 import org.apache.commons.jcs4.engine.behavior.ICacheElement;
 import org.apache.commons.jcs4.engine.behavior.ICompositeCacheAttributes;
@@ -35,7 +34,6 @@ import 
org.apache.commons.jcs4.engine.memory.AbstractMemoryCache;
 import org.apache.commons.jcs4.engine.memory.util.MemoryElementDescriptor;
 import 
org.apache.commons.jcs4.engine.memory.util.SoftReferenceElementDescriptor;
 import org.apache.commons.jcs4.engine.stats.behavior.IStats;
-import org.apache.commons.jcs4.log.Log;
 
 /**
  * A JCS IMemoryCache that has {@link SoftReference} to all its values.
@@ -48,8 +46,10 @@ import org.apache.commons.jcs4.log.Log;
  */
 public class SoftReferenceMemoryCache<K, V> extends AbstractMemoryCache<K, V>
 {
-    /** The logger. */
-    private static final Log log = Log.getLog(SoftReferenceMemoryCache.class);
+    static
+    {
+        cacheImplementationName = "SoftReference Memory Cache";
+    }
 
     /**
      * Strong references to the maxObjects number of newest objects.
@@ -64,41 +64,29 @@ public class SoftReferenceMemoryCache<K, V> extends 
AbstractMemoryCache<K, V>
      * @see 
org.apache.commons.jcs4.engine.memory.AbstractMemoryCache#createMap()
      */
     @Override
-    public ConcurrentMap<K, MemoryElementDescriptor<K, V>> createMap()
+    protected Map<K, MemoryElementDescriptor<K, V>> createMap()
     {
         return new ConcurrentHashMap<>();
     }
 
-    /**
-     * This can't be implemented.
-     *
-     * @param numberToFree
-     * @return 0
-     * @throws IOException
-     */
-    @Override
-    public int freeElements(final int numberToFree) throws IOException
-    {
-        return 0;
-    }
-
     /**
      * @see 
org.apache.commons.jcs4.engine.memory.behavior.IMemoryCache#getKeySet()
      */
     @Override
     public Set<K> getKeySet()
     {
-        final Set<K> keys = new HashSet<>();
-        for (final Map.Entry<K, MemoryElementDescriptor<K, V>> e : 
map.entrySet())
+        lock.readLock().lock();
+        try
         {
-            final SoftReferenceElementDescriptor<K, V> sred = 
(SoftReferenceElementDescriptor<K, V>) e.getValue();
-            if (sred.getCacheElement() != null)
-            {
-                keys.add(e.getKey());
-            }
+            return map.entrySet().stream()
+                    .filter(e -> e.getValue().getCacheElement() != null)
+                    .map(e -> e.getKey())
+                    .collect(Collectors.toSet());
+        }
+        finally
+        {
+            lock.readLock().unlock();
         }
-
-        return keys;
     }
 
     /**
@@ -109,16 +97,19 @@ public class SoftReferenceMemoryCache<K, V> extends 
AbstractMemoryCache<K, V>
     @Override
     public int getSize()
     {
-        int size = 0;
-        for (final MemoryElementDescriptor<K, V> me : map.values())
+        lock.readLock().lock();
+        try
         {
-            final SoftReferenceElementDescriptor<K, V> sred = 
(SoftReferenceElementDescriptor<K, V>) me;
-            if (sred.getCacheElement() != null)
-            {
-                size++;
-            }
+            long size = map.values().stream()
+                    .filter(v -> v.getCacheElement() != null)
+                    .count();
+
+            return (int) size;
+        }
+        finally
+        {
+            lock.readLock().unlock();
         }
-        return size;
     }
 
     /**
@@ -128,9 +119,8 @@ public class SoftReferenceMemoryCache<K, V> extends 
AbstractMemoryCache<K, V>
     public IStats getStatistics()
     {
         final IStats stats = super.getStatistics();
-        stats.setTypeName("Soft Reference Memory Cache");
 
-        final int emptyrefs = map.size() - getSize();
+        final int emptyrefs = super.getSize() - getSize();
         stats.addStatElement("Empty References", Integer.valueOf(emptyrefs));
         stats.addStatElement("Strong References", 
Integer.valueOf(strongReferences.size()));
 
@@ -145,10 +135,20 @@ public class SoftReferenceMemoryCache<K, V> extends 
AbstractMemoryCache<K, V>
     @Override
     public synchronized void initialize( final CompositeCache<K, V> hub )
     {
-        super.initialize( hub );
         strongReferences = new LinkedBlockingQueue<>();
-        log.info( "initialized Soft Reference Memory Cache for {0}",
-                this::getCacheName );
+        super.initialize( hub );
+    }
+
+    /**
+     * Wrap the cache element into an appropriate memory element descriptor
+     *
+     * @param ce The cache element
+     * @return The memory element descriptor
+     */
+    @Override
+    protected MemoryElementDescriptor<K, V> wrap(ICacheElement<K, V> ce)
+    {
+        return new SoftReferenceElementDescriptor<>(ce);
     }
 
     /**
@@ -168,6 +168,25 @@ public class SoftReferenceMemoryCache<K, V> extends 
AbstractMemoryCache<K, V>
         trimStrongReferences();
     }
 
+    /**
+     * Update control structures after update
+     * (guarded by the lock)
+     *
+     * @param newNode The memory element descriptor of the current cache 
element
+     * @param oldNode The memory element descriptor of the previous cache 
element
+     */
+    @Override
+    protected void lockedUpdateElement(MemoryElementDescriptor<K, V> newNode,
+            MemoryElementDescriptor<K, V> oldNode)
+    {
+        final ICacheElement<K, V> val = newNode.getCacheElement();
+        val.elementAttributes().setLastAccessTimeNow();
+
+        // update the ordering of the strong references
+        strongReferences.add(val);
+        trimStrongReferences();
+    }
+
     /**
      * Removes all cached items from the cache control structures.
      * (guarded by the lock)
@@ -190,6 +209,19 @@ public class SoftReferenceMemoryCache<K, V> extends 
AbstractMemoryCache<K, V>
         strongReferences.remove(me.getCacheElement());
     }
 
+    /**
+     * This can't be implemented.
+     *
+     * @param numberToFree
+     * @return 0
+     * @throws IOException
+     */
+    @Override
+    protected int lockedFreeElements(final int numberToFree) throws IOException
+    {
+        return 0;
+    }
+
     /**
      * Trim the number of strong references to equal or below the number given
      * by the maxObjects parameter.
@@ -205,29 +237,4 @@ public class SoftReferenceMemoryCache<K, V> extends 
AbstractMemoryCache<K, V>
             waterfall(ce);
         }
     }
-
-    /**
-     * Puts an item to the cache.
-     *
-     * @param ce Description of the Parameter
-     * @throws IOException Description of the Exception
-     */
-    @Override
-    public void update(final ICacheElement<K, V> ce) throws IOException
-    {
-        lock.lock();
-
-        try
-        {
-            super.update(ce);
-            ce.elementAttributes().setLastAccessTimeNow();
-            map.put(ce.key(), new SoftReferenceElementDescriptor<>(ce));
-            strongReferences.add(ce);
-            trimStrongReferences();
-        }
-        finally
-        {
-            lock.unlock();
-        }
-    }
 }
diff --git 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/utils/struct/DoubleLinkedList.java
 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/utils/struct/DoubleLinkedList.java
index b9cc5c1b..c76b2f6d 100644
--- 
a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/utils/struct/DoubleLinkedList.java
+++ 
b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/utils/struct/DoubleLinkedList.java
@@ -48,7 +48,7 @@ import org.apache.commons.jcs4.log.Log;
  * @see java.util.concurrent.locks.ReentrantLock
  * @see 
org.apache.commons.jcs4.engine.memory.AbstractDoubleLinkedListMemoryCache
  */
-@SuppressWarnings({ "unchecked", "rawtypes" }) // Don't know how to resolve 
this with generics
+@SuppressWarnings({"unchecked", "rawtypes"}) // Don't know how to resolve this 
with generics
 public class DoubleLinkedList<T extends DoubleLinkedListNode>
 {
     /** The logger */
@@ -63,6 +63,17 @@ public class DoubleLinkedList<T extends DoubleLinkedListNode>
     /** LRU double linked list tail node */
     private T last;
 
+    /**
+     * Construct DoubleLinkedList
+     */
+    public DoubleLinkedList()
+    {
+        this.first = (T) new DoubleLinkedListNode<T>(null);
+        this.last = (T) new DoubleLinkedListNode<T>(null);
+        this.first.next = this.last;
+        this.last.prev = this.first;
+    }
+
     /**
      * Adds a new node to the start of the link list.
      *
@@ -70,17 +81,10 @@ public class DoubleLinkedList<T extends 
DoubleLinkedListNode>
      */
     public void addFirst(final T me)
     {
-        if ( last == null )
-        {
-            // empty list.
-            last = me;
-        }
-        else
-        {
-            first.prev = me;
-            me.next = first;
-        }
-        first = me;
+        me.prev = first;
+        me.next = first.next;
+        first.next.prev = me;
+        first.next = me;
         size++;
     }
 
@@ -91,17 +95,10 @@ public class DoubleLinkedList<T extends 
DoubleLinkedListNode>
      */
     public void addLast(final T me)
     {
-        if ( first == null )
-        {
-            // empty list.
-            first = me;
-        }
-        else
-        {
-            last.next = me;
-            me.prev = last;
-        }
-        last = me;
+        me.next = last;
+        me.prev = last.prev;
+        last.prev.next = me;
+        last.prev = me;
         size++;
     }
 
@@ -114,7 +111,7 @@ public class DoubleLinkedList<T extends 
DoubleLinkedListNode>
         if ( log.isDebugEnabled() )
         {
             log.debug( "dumping Entries" );
-            for (T me = first; me != null; me = (T) me.next)
+            for (T me = (T) first.next; me != last; me = (T) me.next)
             {
                 log.debug( "dump Entries> payload= \"{0}\"", me.getPayload() );
             }
@@ -128,8 +125,8 @@ public class DoubleLinkedList<T extends 
DoubleLinkedListNode>
      */
     public T getFirst()
     {
-        log.debug( "returning first node" );
-        return first;
+        log.trace( "returning first node" );
+        return (T) first.next;
     }
 
     /**
@@ -139,8 +136,8 @@ public class DoubleLinkedList<T extends 
DoubleLinkedListNode>
      */
     public T getLast()
     {
-        log.debug( "returning last node" );
-        return last;
+        log.trace( "returning last node" );
+        return (T) last.prev;
     }
 
     /**
@@ -150,29 +147,12 @@ public class DoubleLinkedList<T extends 
DoubleLinkedListNode>
      */
     public void makeFirst(final T ln)
     {
-        if ( ln.prev == null )
-        {
-            // already the first node. or not a node
-            return;
-        }
-        // splice: remove it from the list
         ln.prev.next = ln.next;
-
-        if ( ln.next == null )
-        {
-            // last but not the first.
-            last = (T) ln.prev;
-            last.next = null;
-        }
-        else
-        {
-            // neither the last nor the first.
-            ln.next.prev = ln.prev;
-        }
-        first.prev = ln;
-        ln.next = first;
-        ln.prev = null;
-        first = ln;
+        ln.next.prev = ln.prev;
+        ln.prev = first;
+        ln.next = first.next;
+        first.next.prev = ln;
+        first.next = ln;
     }
 
     /**
@@ -182,29 +162,12 @@ public class DoubleLinkedList<T extends 
DoubleLinkedListNode>
      */
     public void makeLast(final T ln)
     {
-        if ( ln.next == null )
-        {
-            // already the last node. or not a node
-            return;
-        }
-        // splice: remove it from the list
-        if ( ln.prev != null )
-        {
-            ln.prev.next = ln.next;
-        }
-        else
-        {
-            // first
-            first = last;
-        }
-
-        if ( last != null )
-        {
-            last.next = ln;
-        }
-        ln.prev = last;
-        ln.next = null;
-        last = ln;
+        ln.prev.next = ln.next;
+        ln.next.prev = ln.prev;
+        ln.next = last;
+        ln.prev = last.prev;
+        last.prev.next = ln;
+        last.prev = ln;
     }
 
     /**
@@ -215,44 +178,10 @@ public class DoubleLinkedList<T extends 
DoubleLinkedListNode>
      */
     public boolean remove(final T me)
     {
-        log.debug( "removing node" );
-
-        if ( me.next == null )
-        {
-            if ( me.prev == null )
-            {
-                // Make sure it really is the only node before setting head and
-                // tail to null. It is possible that we will be passed a node
-                // which has already been removed from the list, in which case
-                // we should ignore it
-
-                if ( me == first && me == last )
-                {
-                    first = last = null;
-                }
-            }
-            else
-            {
-                // last but not the first.
-                last = (T) me.prev;
-                last.next = null;
-                me.prev = null;
-            }
-        }
-        else if ( me.prev == null )
-        {
-            // first but not the last.
-            first = (T) me.next;
-            first.prev = null;
-            me.next = null;
-        }
-        else
-        {
-            // neither the first nor the last.
-            me.prev.next = me.next;
-            me.next.prev = me.prev;
-            me.prev = me.next = null;
-        }
+        log.trace("removing node");
+        me.prev.next = me.next;
+        me.next.prev = me.prev;
+        me.prev = me.next = null;
         size--;
 
         return true;
@@ -263,15 +192,14 @@ public class DoubleLinkedList<T extends 
DoubleLinkedListNode>
      */
     public void removeAll()
     {
-        for (T me = first; me != null; )
+        for (T me = (T) first.next; me != null;)
         {
-            if ( me.prev != null )
-            {
-                me.prev = null;
-            }
+            me.prev = null;
+            me.next = null;
             me = (T) me.next;
         }
-        first = last = null;
+        first.next = last;
+        last.prev = first;
         size = 0;
     }
 
@@ -282,11 +210,11 @@ public class DoubleLinkedList<T extends 
DoubleLinkedListNode>
      */
     public T removeLast()
     {
-        log.debug( "removing last node" );
-        final T temp = last;
-        if ( last != null )
+        log.trace("removing last node");
+        final T temp = (T) last.prev;
+        if (last != first)
         {
-            remove( last );
+            remove(temp);
         }
         return temp;
     }
diff --git 
a/commons-jcs4-core/src/test/java/org/apache/commons/jcs4/engine/memory/MockMemoryCache.java
 
b/commons-jcs4-core/src/test/java/org/apache/commons/jcs4/engine/memory/MockMemoryCache.java
index 2fc2b8fc..17bcbb7e 100644
--- 
a/commons-jcs4-core/src/test/java/org/apache/commons/jcs4/engine/memory/MockMemoryCache.java
+++ 
b/commons-jcs4-core/src/test/java/org/apache/commons/jcs4/engine/memory/MockMemoryCache.java
@@ -95,23 +95,6 @@ public class MockMemoryCache<K, V>
         return cacheAttr;
     }
 
-    /**
-     * @param group
-     * @return null
-     */
-    public Set<K> getGroupKeys( final String group )
-    {
-        return null;
-    }
-
-    /**
-     * @return null
-     */
-    public Set<String> getGroupNames()
-    {
-        return null;
-    }
-
     /**
      * @return map.keySet().toArray( */
     @Override
@@ -204,15 +187,6 @@ public class MockMemoryCache<K, V>
         map.clear();
     }
 
-    /**
-     * @param cattr
-     */
-    @Override
-    public void setCacheAttributes( final ICompositeCacheAttributes cattr )
-    {
-        this.cacheAttr = cattr;
-    }
-
     /**
      * @param ce
      * @throws IOException
diff --git 
a/commons-jcs4-core/src/test/java/org/apache/commons/jcs4/engine/memory/mru/MRUMemoryCacheUnitTest.java
 
b/commons-jcs4-core/src/test/java/org/apache/commons/jcs4/engine/memory/mru/MRUMemoryCacheUnitTest.java
index c49ee88f..812d0f66 100644
--- 
a/commons-jcs4-core/src/test/java/org/apache/commons/jcs4/engine/memory/mru/MRUMemoryCacheUnitTest.java
+++ 
b/commons-jcs4-core/src/test/java/org/apache/commons/jcs4/engine/memory/mru/MRUMemoryCacheUnitTest.java
@@ -20,6 +20,7 @@ package org.apache.commons.jcs4.engine.memory.mru;
  */
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -27,6 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
 
 import org.apache.commons.jcs4.JCS;
 import org.apache.commons.jcs4.access.CacheAccess;
@@ -35,6 +37,10 @@ import org.apache.commons.jcs4.engine.CacheElement;
 import org.apache.commons.jcs4.engine.behavior.ICacheElement;
 import org.apache.commons.jcs4.engine.control.CompositeCache;
 import org.apache.commons.jcs4.engine.control.CompositeCacheManager;
+import org.apache.commons.jcs4.engine.stats.behavior.ICacheStats;
+import org.apache.commons.jcs4.engine.stats.behavior.IStatElement;
+import org.apache.commons.jcs4.engine.stats.behavior.IStats;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -50,6 +56,12 @@ class MRUMemoryCacheUnitTest
         JCS.setConfigFilename( "/TestMRUCache.ccf" );
     }
 
+    @AfterEach
+    void tearDown()
+    {
+        JCS.shutdown();
+    }
+
     /**
      * put the max and clear. verify that no elements remain.
      *
@@ -130,10 +142,21 @@ class MRUMemoryCacheUnitTest
             cache.put( i + ":key", "myregion data " + i );
         }
 
-        final String stats = cache.getStatistics().toString();
-
-        // TODO improve stats check
-        assertTrue( stats.indexOf( "2000" ) != -1, "Should have 200 puts" );
+        final ICacheStats stats = cache.getStatistics();
+        boolean found = false;
+        for (IStats s : stats.getAuxiliaryCacheStats())
+        {
+            for (IStatElement<?> e : s.getStatElements())
+            {
+                if ("Put Count".equals(e.name()))
+                {
+                    found = true;
+                    assertInstanceOf(AtomicLong.class, e.data());
+                    assertEquals(items, ((AtomicLong) e.data()).get(), "Should 
have " + items + " puts");
+                }
+            }
+        }
+        assertTrue(found, "Stats should contain Put Count");
     }
 
     /**

Reply via email to