More streaming, less arrays
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCache.java
index 4cec8d0..4866b43 100644
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCache.java
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/disk/indexed/IndexedDiskCache.java
@@ -357,26 +357,22 @@
         log.debug("{0}: Performing inital consistency check", logCacheName);
 
         boolean isOk = true;
-        long fileLength = 0;
         try
         {
-            fileLength = dataFile.length();
+            final long fileLength = dataFile.length();
 
-            for (final Map.Entry<K, IndexedDiskElementDescriptor> e : keyHash.entrySet())
+            final IndexedDiskElementDescriptor corruptDed = keyHash.values().stream()
+                .filter(ded -> ded.pos + IndexedDisk.HEADER_SIZE_BYTES + ded.len > fileLength)
+                .findFirst()
+                .orElse(null);
+
+            if (corruptDed != null)
             {
-                final IndexedDiskElementDescriptor ded = e.getValue();
-
-                isOk = ded.pos + IndexedDisk.HEADER_SIZE_BYTES + ded.len <= fileLength;
-
-                if (!isOk)
-                {
-                    log.warn("{0}: The dataFile is corrupted!\n raf.length() = {1}\n ded.pos = {2}",
-                            logCacheName, fileLength, ded.pos);
-                    break;
-                }
+                isOk = false;
+                log.warn("{0}: The dataFile is corrupted!\n raf.length() = {1}\n ded.pos = {2}",
+                        logCacheName, fileLength, corruptDed.pos);
             }
-
-            if (isOk && checkForDedOverlaps)
+            else if (checkForDedOverlaps)
             {
                 isOk = checkForDedOverlaps(createPositionSortedDescriptorList());
             }
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWaitFacade.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWaitFacade.java
index a64bb5f..7d86610 100644
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWaitFacade.java
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWaitFacade.java
@@ -27,8 +27,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.stream.Collectors;
 
 import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCache;
@@ -56,9 +56,18 @@
     /** The logger */
     private static final Log log = LogManager.getLog( LateralCacheNoWaitFacade.class );
 
-    /** The queuing facade to the client. */
+    /**
+     * The queuing facade to the client.
+     * @deprecated Should not have been public in the first place
+     */
+    @Deprecated
     public LateralCacheNoWait<K, V>[] noWaits;
 
+    /**
+     * The queuing facade to the client.
+     */
+    private final CopyOnWriteArraySet<LateralCacheNoWait<K, V>> noWaitSet;
+
     /** The region name */
     private final String cacheName;
 
@@ -82,11 +91,22 @@
         log.debug( "CONSTRUCTING NO WAIT FACADE" );
         this.listener = listener;
         this.noWaits = noWaits;
+        this.noWaitSet = new CopyOnWriteArraySet<>(Arrays.asList(noWaits));
         this.cacheName = cattr.getCacheName();
         this.lateralCacheAttributes = cattr;
     }
 
     /**
+     * Return the size of the no wait list (for testing)
+     *
+     * @return the noWait list size.
+     */
+    protected int getNoWaitSize()
+    {
+        return noWaitSet.size();
+    }
+
+    /**
      * Tells you if the no wait is in the list or not.
      * <p>
      * @param noWait
@@ -94,12 +114,7 @@
      */
     public boolean containsNoWait( final LateralCacheNoWait<K, V> noWait )
     {
-        final Optional<LateralCacheNoWait<K, V>> optional = Arrays.stream(noWaits)
-                // we know noWait isn't null
-                .filter(nw -> noWait.equals( nw ))
-                .findFirst();
-
-        return optional.isPresent();
+        return noWaitSet.contains(noWait);
     }
 
     /**
@@ -108,6 +123,7 @@
      * @param noWait
      * @return true if it wasn't already contained
      */
+    @SuppressWarnings("unchecked") // No generic arrays in Java
     public synchronized boolean addNoWait( final LateralCacheNoWait<K, V> noWait )
     {
         if ( noWait == null )
@@ -115,22 +131,15 @@
             return false;
         }
 
-        if ( containsNoWait( noWait ) )
+        final boolean added = noWaitSet.add(noWait);
+
+        if (!added)
         {
             log.debug( "No Wait already contained, [{0}]", noWait );
             return false;
         }
 
-        @SuppressWarnings("unchecked") // No generic arrays in java
-        final
-        LateralCacheNoWait<K, V>[] newArray = new LateralCacheNoWait[noWaits.length + 1];
-
-        System.arraycopy( noWaits, 0, newArray, 0, noWaits.length );
-
-        // set the last position to the new noWait
-        newArray[noWaits.length] = noWait;
-
-        noWaits = newArray;
+        noWaits = noWaitSet.toArray(new LateralCacheNoWait[0]);
 
         return true;
     }
@@ -141,6 +150,7 @@
      * @param noWait
      * @return true if it was already in the array
      */
+    @SuppressWarnings("unchecked") // No generic arrays in java
     public synchronized boolean removeNoWait( final LateralCacheNoWait<K, V> noWait )
     {
         if ( noWait == null )
@@ -148,48 +158,31 @@
             return false;
         }
 
-        int position = -1;
-        for ( int i = 0; i < noWaits.length; i++ )
-        {
-            // we know noWait isn't null
-            if ( noWait.equals( noWaits[i] ) )
-            {
-                position = i;
-                break;
-            }
-        }
+        final boolean contained = noWaitSet.remove(noWait);
 
-        if ( position == -1 )
+        if (!contained)
         {
             return false;
         }
 
-        @SuppressWarnings("unchecked") // No generic arrays in java
-        final
-        LateralCacheNoWait<K, V>[] newArray = new LateralCacheNoWait[noWaits.length - 1];
-
-        System.arraycopy( noWaits, 0, newArray, 0, position );
-        if ( noWaits.length != position )
-        {
-            System.arraycopy( noWaits, position + 1, newArray, position, noWaits.length - position - 1 );
-        }
-        noWaits = newArray;
+        noWaits = noWaitSet.toArray(new LateralCacheNoWait[0]);
 
         return true;
     }
 
     /**
-     * @param ce
+     * Update the cache element in all lateral caches
+     * @param ce the cache element
      * @throws IOException
      */
     @Override
     public void update( final ICacheElement<K, V> ce )
         throws IOException
     {
-        log.debug( "updating through lateral cache facade, noWaits.length = {0}",
-                noWaits.length );
+        log.debug("updating through lateral cache facade, noWaits.length = {0}",
+                () -> noWaitSet.size());
 
-        for (final LateralCacheNoWait<K, V> nw : noWaits)
+        for (final LateralCacheNoWait<K, V> nw : noWaitSet)
         {
             nw.update( ce );
         }
@@ -204,17 +197,11 @@
     @Override
     public ICacheElement<K, V> get( final K key )
     {
-        final Optional<ICacheElement<K, V>> optional = Arrays.stream(noWaits)
-            .map(nw -> nw.get( key ))
+        return noWaitSet.stream()
+            .map(nw -> nw.get(key))
             .filter(obj -> obj != null)
-            .findFirst();
-
-        if (optional.isPresent())
-        {
-            return optional.get();
-        }
-
-        return null;
+            .findFirst()
+            .orElse(null);
     }
 
     /**
@@ -227,7 +214,7 @@
     @Override
     public Map<K, ICacheElement<K, V>> getMultiple(final Set<K> keys)
     {
-        if ( keys != null && !keys.isEmpty() )
+        if (keys != null && !keys.isEmpty())
         {
             final Map<K, ICacheElement<K, V>> elements = keys.stream()
                 .collect(Collectors.toMap(
@@ -254,12 +241,11 @@
     @Override
     public Map<K, ICacheElement<K, V>> getMatching(final String pattern)
     {
-        final Map<K, ICacheElement<K, V>> elements = new HashMap<>();
-        for (final LateralCacheNoWait<K, V> nw : noWaits)
-        {
-            elements.putAll( nw.getMatching( pattern ) );
-        }
-        return elements;
+        return noWaitSet.stream()
+                .flatMap(nw -> nw.getMatching(pattern).entrySet().stream())
+                .collect(Collectors.toMap(
+                        Entry::getKey,
+                        Entry::getValue));
     }
 
     /**
@@ -271,15 +257,12 @@
     public Set<K> getKeySet() throws IOException
     {
         final HashSet<K> allKeys = new HashSet<>();
-        for (final LateralCacheNoWait<K, V> nw : noWaits)
+        for (final LateralCacheNoWait<K, V> nw : noWaitSet)
         {
-            if ( nw != null )
+            final Set<K> keys = nw.getKeySet();
+            if (keys != null)
             {
-                final Set<K> keys = nw.getKeySet();
-                if (keys != null)
-                {
-                    allKeys.addAll( keys );
-                }
+                allKeys.addAll(keys);
             }
         }
         return allKeys;
@@ -294,7 +277,7 @@
     @Override
     public boolean remove( final K key )
     {
-        Arrays.stream(noWaits).forEach(nw -> nw.remove( key ));
+        noWaitSet.forEach(nw -> nw.remove( key ));
         return false;
     }
 
@@ -304,7 +287,7 @@
     @Override
     public void removeAll()
     {
-        Arrays.stream(noWaits).forEach(LateralCacheNoWait::removeAll);
+        noWaitSet.forEach(LateralCacheNoWait::removeAll);
     }
 
     /** Adds a dispose request to the lateral cache. */
@@ -319,7 +302,7 @@
                 listener = null;
             }
 
-            Arrays.stream(noWaits).forEach(LateralCacheNoWait::dispose);
+            noWaitSet.forEach(LateralCacheNoWait::dispose);
         }
         finally
         {
@@ -373,12 +356,12 @@
             return CacheStatus.DISPOSED;
         }
 
-        if (noWaits.length == 0 || listener != null)
+        if (noWaitSet.isEmpty() || listener != null)
         {
             return CacheStatus.ALIVE;
         }
 
-        final List<CacheStatus> statii = Arrays.stream(noWaits)
+        final List<CacheStatus> statii = noWaitSet.stream()
                 .map(LateralCacheNoWait::getStatus)
                 .collect(Collectors.toList());
 
@@ -448,19 +431,13 @@
 
         final ArrayList<IStatElement<?>> elems = new ArrayList<>();
 
-        if ( noWaits != null )
+        if (noWaitSet != null)
         {
-            elems.add(new StatElement<>( "Number of No Waits", Integer.valueOf(noWaits.length) ) );
+            elems.add(new StatElement<>("Number of No Waits", Integer.valueOf(noWaitSet.size())));
 
-            for ( final LateralCacheNoWait<K, V> lcnw : noWaits )
-            {
-                if ( lcnw != null )
-                {
-                    // get the stats from the super too
-                    final IStats sStats = lcnw.getStatistics();
-                    elems.addAll(sStats.getStatElements());
-                }
-            }
+            elems.addAll(noWaitSet.stream()
+                    .flatMap(lcnw -> lcnw.getStatistics().getStatElements().stream())
+                    .collect(Collectors.toList()));
         }
 
         stats.setStatElements( elems );
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPListener.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPListener.java
index 8f457f8..32111f8 100644
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPListener.java
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/auxiliary/lateral/socket/tcp/LateralTCPListener.java
@@ -471,17 +471,11 @@
     {
         try
         {
-            while ( true )
+            // Check to see if we've been asked to exit, and exit
+            while ( !terminated.get() )
             {
                 log.debug( "Waiting for clients to connect " );
 
-                // Check to see if we've been asked to exit, and exit
-                if (terminated.get())
-                {
-                    log.debug("Thread terminated, exiting gracefully");
-                    break;
-                }
-
                 try
                 {
                     final Socket socket = serverSocket.accept();
@@ -499,6 +493,7 @@
                 }
             }
 
+            log.debug("Thread terminated, exiting gracefully");
             serverSocket.close();
         }
         catch ( final IOException e )
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCache.java
index 011fce1..70d9b6f 100644
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCache.java
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCache.java
@@ -21,11 +21,15 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
@@ -82,8 +86,7 @@
     private IElementEventQueue elementEventQ;
 
     /** Auxiliary caches. */
-    @SuppressWarnings("unchecked") // OK because this is an empty array
-    private AuxiliaryCache<K, V>[] auxCaches = new AuxiliaryCache[0];
+    private CopyOnWriteArrayList<AuxiliaryCache<K, V>> auxCaches = new CopyOnWriteArrayList<>();
 
     /** is this alive? */
     private final AtomicBoolean alive;
@@ -186,12 +189,27 @@
 
     /**
      * This sets the list of auxiliary caches for this region.
+     * It filters out null caches
      * <p>
      * @param auxCaches
      */
+    public void setAuxCaches(final List<AuxiliaryCache<K, V>> auxCaches)
+    {
+        this.auxCaches = auxCaches.stream()
+                .filter(aux -> aux != null)
+                .collect(Collectors.toCollection(CopyOnWriteArrayList::new));
+    }
+
+    /**
+     * This sets the list of auxiliary caches for this region.
+     * <p>
+     * @param auxCaches
+     * @deprecated Use List method
+     */
+    @Deprecated
     public void setAuxCaches(final AuxiliaryCache<K, V>[] auxCaches)
     {
-        this.auxCaches = auxCaches;
+        setAuxCaches(Arrays.asList(auxCaches));
     }
 
     /**
@@ -199,12 +217,25 @@
      * <p>
      * @return an array of auxiliary caches, may be empty, never null
      */
-    public AuxiliaryCache<K, V>[] getAuxCaches()
+    public List<AuxiliaryCache<K, V>> getAuxCacheList()
     {
         return this.auxCaches;
     }
 
     /**
+     * Get the list of auxiliary caches for this region.
+     * <p>
+     * @return an array of auxiliary caches, may be empty, never null
+     * @deprecated Use List method
+     */
+    @SuppressWarnings("unchecked") // No generic arrays in Java
+    @Deprecated
+    public AuxiliaryCache<K, V>[] getAuxCaches()
+    {
+        return getAuxCacheList().toArray(new AuxiliaryCache[0]);
+    }
+
+    /**
      * Standard update method.
      * <p>
      * @param ce
@@ -286,7 +317,7 @@
         // more can be added if future auxiliary caches don't fit the model
         // You could run a database cache as either a remote or a local disk.
         // The types would describe the purpose.
-        if (auxCaches.length > 0)
+        if (!auxCaches.isEmpty())
         {
             log.debug("Updating auxiliary caches");
         }
@@ -385,7 +416,7 @@
         // SPOOL TO DISK.
         for (final ICache<K, V> aux : auxCaches)
         {
-            if (aux != null && aux.getCacheType() == CacheType.DISK_CACHE)
+            if (aux.getCacheType() == CacheType.DISK_CACHE)
             {
                 diskAvailable = true;
 
@@ -496,56 +527,53 @@
                 // caches, even if not local look in disk auxiliaries
                 for (final AuxiliaryCache<K, V> aux : auxCaches)
                 {
-                    if (aux != null)
+                    final CacheType cacheType = aux.getCacheType();
+
+                    if (!localOnly || cacheType == CacheType.DISK_CACHE)
                     {
-                        final CacheType cacheType = aux.getCacheType();
+                        log.debug("Attempting to get from aux [{0}] which is of type: {1}",
+                                () -> aux.getCacheName(), () -> cacheType);
 
-                        if (!localOnly || cacheType == CacheType.DISK_CACHE)
+                        try
                         {
-                            log.debug("Attempting to get from aux [{0}] which is of type: {1}",
-                                    () -> aux.getCacheName(), () -> cacheType);
+                            element = aux.get(key);
+                        }
+                        catch (final IOException e)
+                        {
+                            log.error("Error getting from aux", e);
+                        }
+                    }
 
-                            try
-                            {
-                                element = aux.get(key);
-                            }
-                            catch (final IOException e)
-                            {
-                                log.error("Error getting from aux", e);
-                            }
+                    log.debug("Got CacheElement: {0}", element);
+
+                    // Item found in one of the auxiliary caches.
+                    if (element != null)
+                    {
+                        if (isExpired(element))
+                        {
+                            log.debug("{0} - Aux cache[{1}] hit, but element expired.",
+                                    () -> cacheAttr.getCacheName(), () -> aux.getCacheName());
+
+                            // This will tell the remotes to remove the item
+                            // based on the element's expiration policy. The elements attributes
+                            // associated with the item when it created govern its behavior
+                            // everywhere.
+                            doExpires(element);
+                            element = null;
+                        }
+                        else
+                        {
+                            log.debug("{0} - Aux cache[{1}] hit.",
+                                    () -> cacheAttr.getCacheName(), () -> aux.getCacheName());
+
+                            // Update counters
+                            hitCountAux.incrementAndGet();
+                            copyAuxiliaryRetrievedItemToMemory(element);
                         }
 
-                        log.debug("Got CacheElement: {0}", element);
+                        found = true;
 
-                        // Item found in one of the auxiliary caches.
-                        if (element != null)
-                        {
-                            if (isExpired(element))
-                            {
-                                log.debug("{0} - Aux cache[{1}] hit, but element expired.",
-                                        () -> cacheAttr.getCacheName(), () -> aux.getCacheName());
-
-                                // This will tell the remotes to remove the item
-                                // based on the element's expiration policy. The elements attributes
-                                // associated with the item when it created govern its behavior
-                                // everywhere.
-                                doExpires(element);
-                                element = null;
-                            }
-                            else
-                            {
-                                log.debug("{0} - Aux cache[{1}] hit.",
-                                        () -> cacheAttr.getCacheName(), () -> aux.getCacheName());
-
-                                // Update counters
-                                hitCountAux.incrementAndGet();
-                                copyAuxiliaryRetrievedItemToMemory(element);
-                            }
-
-                            found = true;
-
-                            break;
-                        }
+                        break;
                     }
                 }
             }
@@ -699,41 +727,38 @@
 
         for (final AuxiliaryCache<K, V> aux : auxCaches)
         {
-            if (aux != null)
+            final Map<K, ICacheElement<K, V>> elementsFromAuxiliary =
+                new HashMap<>();
+
+            final CacheType cacheType = aux.getCacheType();
+
+            if (!localOnly || cacheType == CacheType.DISK_CACHE)
             {
-                final Map<K, ICacheElement<K, V>> elementsFromAuxiliary =
-                    new HashMap<>();
+                log.debug("Attempting to get from aux [{0}] which is of type: {1}",
+                        () -> aux.getCacheName(), () -> cacheType);
 
-                final CacheType cacheType = aux.getCacheType();
-
-                if (!localOnly || cacheType == CacheType.DISK_CACHE)
+                try
                 {
-                    log.debug("Attempting to get from aux [{0}] which is of type: {1}",
-                            () -> aux.getCacheName(), () -> cacheType);
-
-                    try
-                    {
-                        elementsFromAuxiliary.putAll(aux.getMultiple(remainingKeys));
-                    }
-                    catch (final IOException e)
-                    {
-                        log.error("Error getting from aux", e);
-                    }
+                    elementsFromAuxiliary.putAll(aux.getMultiple(remainingKeys));
                 }
-
-                log.debug("Got CacheElements: {0}", elementsFromAuxiliary);
-
-                processRetrievedElements(aux, elementsFromAuxiliary);
-                elements.putAll(elementsFromAuxiliary);
-
-                if (elements.size() == keys.size())
+                catch (final IOException e)
                 {
-                    break;
+                    log.error("Error getting from aux", e);
                 }
-                else
-                {
-                    remainingKeys = pruneKeysFound(keys, elements);
-                }
+            }
+
+            log.debug("Got CacheElements: {0}", elementsFromAuxiliary);
+
+            processRetrievedElements(aux, elementsFromAuxiliary);
+            elements.putAll(elementsFromAuxiliary);
+
+            if (elements.size() == keys.size())
+            {
+                break;
+            }
+            else
+            {
+                remainingKeys = pruneKeysFound(keys, elements);
             }
         }
 
@@ -840,36 +865,33 @@
     {
         final Map<K, ICacheElement<K, V>> elements = new HashMap<>();
 
-        for (int i = auxCaches.length - 1; i >= 0; i--)
+        for (ListIterator<AuxiliaryCache<K, V>> i = auxCaches.listIterator(auxCaches.size()); i.hasPrevious();)
         {
-            final AuxiliaryCache<K, V> aux = auxCaches[i];
+            final AuxiliaryCache<K, V> aux = i.previous();
 
-            if (aux != null)
+            final Map<K, ICacheElement<K, V>> elementsFromAuxiliary =
+                new HashMap<>();
+
+            final CacheType cacheType = aux.getCacheType();
+
+            if (!localOnly || cacheType == CacheType.DISK_CACHE)
             {
-                final Map<K, ICacheElement<K, V>> elementsFromAuxiliary =
-                    new HashMap<>();
+                log.debug("Attempting to get from aux [{0}] which is of type: {1}",
+                        () -> aux.getCacheName(), () -> cacheType);
 
-                final CacheType cacheType = aux.getCacheType();
-
-                if (!localOnly || cacheType == CacheType.DISK_CACHE)
+                try
                 {
-                    log.debug("Attempting to get from aux [{0}] which is of type: {1}",
-                            () -> aux.getCacheName(), () -> cacheType);
-
-                    try
-                    {
-                        elementsFromAuxiliary.putAll(aux.getMatching(pattern));
-                    }
-                    catch (final IOException e)
-                    {
-                        log.error("Error getting from aux", e);
-                    }
-
-                    log.debug("Got CacheElements: {0}", elementsFromAuxiliary);
-
-                    processRetrievedElements(aux, elementsFromAuxiliary);
-                    elements.putAll(elementsFromAuxiliary);
+                    elementsFromAuxiliary.putAll(aux.getMatching(pattern));
                 }
+                catch (final IOException e)
+                {
+                    log.error("Error getting from aux", e);
+                }
+
+                log.debug("Got CacheElements: {0}", elementsFromAuxiliary);
+
+                processRetrievedElements(aux, elementsFromAuxiliary);
+                elements.putAll(elementsFromAuxiliary);
             }
         }
 
@@ -982,26 +1004,19 @@
      */
     public Set<K> getKeySet(final boolean localOnly)
     {
-        final HashSet<K> allKeys = new HashSet<>(memCache.getKeySet());
-
-        for (final AuxiliaryCache<K, V> aux : auxCaches)
-        {
-            if (aux != null)
-            {
-                if(!localOnly || aux.getCacheType() == CacheType.DISK_CACHE)
+        return Stream.concat(memCache.getKeySet().stream(), auxCaches.stream()
+            .filter(aux -> !localOnly || aux.getCacheType() == CacheType.DISK_CACHE)
+            .flatMap(aux -> {
+                try
                 {
-                    try
-                    {
-                        allKeys.addAll(aux.getKeySet());
-                    }
-                    catch (final IOException e)
-                    {
-                        // ignore
-                    }
+                    return aux.getKeySet().stream();
                 }
-            }
-        }
-        return allKeys;
+                catch (final IOException e)
+                {
+                    return Stream.of();
+                }
+            }))
+            .collect(Collectors.toSet());
     }
 
     /**
@@ -1142,10 +1157,9 @@
         }
 
         // Removes from all auxiliary disk caches.
-        for (final ICache<K, V> aux : auxCaches)
-        {
-            if (aux != null && (aux.getCacheType() == CacheType.DISK_CACHE || !localOnly))
-            {
+        auxCaches.stream()
+            .filter(aux -> aux.getCacheType() == CacheType.DISK_CACHE || !localOnly)
+            .forEach(aux -> {
                 try
                 {
                     log.debug("Removing All keys from cacheType {0}",
@@ -1155,10 +1169,9 @@
                 }
                 catch (final IOException ex)
                 {
-                    log.error("Failure removing all from aux", ex);
+                    log.error("Failure removing all from aux " + aux, ex);
                 }
-            }
-        }
+            });
     }
 
     /**
@@ -1274,16 +1287,15 @@
      */
     public void save()
     {
-        if (alive.compareAndSet(true, false) == false)
+        if (!alive.get())
         {
             return;
         }
 
-        for (final ICache<K, V> aux : auxCaches)
-        {
-            try
-            {
-                if (aux.getStatus() == CacheStatus.ALIVE)
+        auxCaches.stream()
+            .filter(aux -> aux.getStatus() == CacheStatus.ALIVE)
+            .forEach(aux -> {
+                try
                 {
                     for (final K key : memCache.getKeySet())
                     {
@@ -1295,12 +1307,11 @@
                         }
                     }
                 }
-            }
-            catch (final IOException ex)
-            {
-                log.error("Failure saving aux caches.", ex);
-            }
-        }
+                catch (final IOException ex)
+                {
+                    log.error("Failure saving aux caches.", ex);
+                }
+            });
 
         log.debug("Called save for [{0}]", () -> cacheAttr.getCacheName());
     }
@@ -1369,15 +1380,12 @@
         stats.setStatElements(elems);
 
         // memory + aux, memory is not considered an auxiliary internally
-        final int total = auxCaches.length + 1;
-        final ArrayList<IStats> auxStats = new ArrayList<>(total);
+        final ArrayList<IStats> auxStats = new ArrayList<>(auxCaches.size() + 1);
 
         auxStats.add(getMemoryCache().getStatistics());
-
-        for (final AuxiliaryCache<K, V> aux : auxCaches)
-        {
-            auxStats.add(aux.getStatistics());
-        }
+        auxStats.addAll(auxCaches.stream()
+                .map(AuxiliaryCache::getStatistics)
+                .collect(Collectors.toList()));
 
         // store the auxiliary stats
         stats.setAuxiliaryCacheStats(auxStats);
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCacheConfigurator.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCacheConfigurator.java
index 5ecae58..f50ed6b 100644
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCacheConfigurator.java
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/control/CompositeCacheConfigurator.java
@@ -267,10 +267,7 @@
             }
 
             // Associate the auxiliaries with the cache
-            @SuppressWarnings("unchecked") // No generic arrays in java
-            final
-            AuxiliaryCache<K, V>[] auxArray = auxList.toArray( new AuxiliaryCache[0] );
-            cache.setAuxCaches( auxArray );
+            cache.setAuxCaches(auxList);
         }
 
         // Return the new cache
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/AbstractDoubleLinkedListMemoryCache.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/AbstractDoubleLinkedListMemoryCache.java
index 422530d..ca533ff 100644
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/AbstractDoubleLinkedListMemoryCache.java
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/engine/memory/AbstractDoubleLinkedListMemoryCache.java
@@ -162,14 +162,7 @@
 
         try
         {
-            for (int i = 0; i < chunkSizeCorrected; i++)
-            {
-                final ICacheElement<K, V> lastElement = spoolLastElement();
-                if (lastElement == null)
-                {
-                    break;
-                }
-            }
+            freeElements(chunkSizeCorrected);
 
             // If this is out of the sync block it can detect a mismatch
             // where there is none.
@@ -197,10 +190,9 @@
      * @param numberToFree
      * @return the number that were removed. if you ask to free 5, but there are only 3, you will
      *         get 3.
-     * @throws IOException
      */
     @Override
-    public int freeElements(final int numberToFree) throws IOException
+    public int freeElements(final int numberToFree)
     {
         int freed = 0;
 
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryService.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryService.java
index fcb301a..bbe4b4e 100644
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryService.java
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/discovery/UDPDiscoveryService.java
@@ -24,6 +24,8 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
@@ -65,7 +67,7 @@
     private AtomicBoolean shutdown = new AtomicBoolean(false);
 
     /** This is a set of services that have been discovered. */
-    private final Set<DiscoveredService> discoveredServices = new CopyOnWriteArraySet<>();
+    private final ConcurrentMap<Integer, DiscoveredService> discoveredServices = new ConcurrentHashMap<>();
 
     /** This a list of regions that are configured to use discovery. */
     private final Set<String> cacheNames = new CopyOnWriteArraySet<>();
@@ -260,9 +262,7 @@
      */
     public void removeDiscoveredService( final DiscoveredService service )
     {
-        final boolean contained = getDiscoveredServices().remove( service );
-
-        if ( contained )
+        if (discoveredServices.remove(service.hashCode()) != null)
         {
             log.info( "Removing {0}", service );
         }
@@ -277,39 +277,29 @@
      */
     protected void addOrUpdateService( final DiscoveredService discoveredService )
     {
-        final Set<DiscoveredService> discoveredServices = getDiscoveredServices();
-        // Since this is a set we can add it over an over.
         // We want to replace the old one, since we may add info that is not part of the equals.
         // The equals method on the object being added is intentionally restricted.
-        if ( !discoveredServices.contains( discoveredService ) )
-        {
-            log.info( "Set does not contain service. I discovered {0}", discoveredService );
-            log.debug( "Adding service in the set {0}", discoveredService );
-            discoveredServices.add( discoveredService );
-        }
-        else
-        {
+        discoveredServices.merge(discoveredService.hashCode(), discoveredService, (oldService, newService) -> {
             log.debug( "Set contains service." );
-            log.debug( "Updating service in the set {0}", discoveredService );
+            log.debug( "Updating service in the set {0}", newService );
 
             // Update the list of cache names if it has changed.
             // need to update the time this sucks. add has no effect convert to a map
-            DiscoveredService theOldServiceInformation = discoveredServices.stream()
-                .filter(service -> discoveredService.equals(service))
-                .findFirst()
-                .orElse(null);
-
-            if (theOldServiceInformation != null &&
-                !theOldServiceInformation.getCacheNames().equals(discoveredService.getCacheNames()))
+            if (!oldService.getCacheNames().equals(newService.getCacheNames()))
             {
-                log.info( "List of cache names changed for service: {0}",
-                        discoveredService );
+                log.info( "List of cache names changed for service: {0}", newService );
+
+                // replace it, we want to reset the payload and the last heard from time.
+                return newService;
             }
 
-            // replace it, we want to reset the payload and the last heard from time.
-            discoveredServices.remove( discoveredService );
-            discoveredServices.add( discoveredService );
-        }
+            if (oldService.getLastHearFromTime() != newService.getLastHearFromTime())
+            {
+                return newService;
+            }
+
+            return oldService;
+        });
 
         // Always Notify the listeners
         // If we don't do this, then if a region using the default config is initialized after notification,
@@ -397,7 +387,7 @@
      */
     public Set<DiscoveredService> getDiscoveredServices()
     {
-        return discoveredServices;
+        return new HashSet<>(discoveredServices.values());
     }
 
     /**
diff --git a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/AbstractLRUMap.java b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/AbstractLRUMap.java
index b1a247d..53b56b0 100644
--- a/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/AbstractLRUMap.java
+++ b/commons-jcs-core/src/main/java/org/apache/commons/jcs3/utils/struct/AbstractLRUMap.java
@@ -441,19 +441,16 @@
         }
 
         log.trace( "verifycache: checking via keysets!" );
-        map.forEach((key, value) -> {
-            boolean found = false;
-
-            for (LRUElementDescriptor<K, V> li2 = list.getFirst(); li2 != null; li2 = (LRUElementDescriptor<K, V>) li2.next )
-            {
-                if ( key.equals( li2.getKey() ) )
+        map.keySet().stream()
+            .filter(key -> {
+                for (LRUElementDescriptor<K, V> li2 = list.getFirst(); li2 != null; li2 = (LRUElementDescriptor<K, V>) li2.next )
                 {
-                    found = true;
-                    break;
+                    if ( key.equals( li2.getKey() ) )
+                    {
+                        return true;
+                    }
                 }
-            }
-            if ( !found )
-            {
+
                 log.error( "verifycache: key not found in list : {0}", key );
                 dumpCacheEntries();
                 if ( map.containsKey( key ) )
@@ -464,8 +461,10 @@
                 {
                     log.error( "verifycache: map does NOT contain key, what the HECK!" );
                 }
-            }
-        });
+
+                return false;
+            })
+            .findFirst();
     }
 
     /**
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWaitFacadeUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWaitFacadeUnitTest.java
index 02eb6f8..a8e6133 100644
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWaitFacadeUnitTest.java
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/auxiliary/lateral/LateralCacheNoWaitFacadeUnitTest.java
@@ -56,7 +56,7 @@
         facade.removeNoWait( noWait );
 
         // VERIFY
-        assertEquals( "Should have 0", 0, facade.noWaits.length );
+        assertEquals( "Should have 0", 0, facade.getNoWaitSize() );
         assertFalse( "Should not be in the list. ", facade.containsNoWait( noWait ) );
     }
 
@@ -83,7 +83,7 @@
         facade.addNoWait( noWait2 );
 
         // VERIFY
-        assertEquals( "Should have 2", 2, facade.noWaits.length );
+        assertEquals( "Should have 2", 2, facade.getNoWaitSize() );
         assertTrue( "Should be in the list.", facade.containsNoWait( noWait ) );
         assertTrue( "Should be in the list.", facade.containsNoWait( noWait2 ) );
 
@@ -91,7 +91,7 @@
         facade.removeNoWait( noWait );
 
         // VERIFY
-        assertEquals( "Should only have 1", 1, facade.noWaits.length );
+        assertEquals( "Should only have 1", 1, facade.getNoWaitSize() );
         assertFalse( "Should not be in the list. ", facade.containsNoWait( noWait ) );
         assertTrue( "Should be in the list.", facade.containsNoWait( noWait2 ) );
     }
@@ -119,7 +119,7 @@
 
         // VERIFY
         assertTrue( "Should be in the list.", facade.containsNoWait( noWait ) );
-        assertEquals( "Should only have 1", 1, facade.noWaits.length );
+        assertEquals( "Should only have 1", 1, facade.getNoWaitSize() );
     }
 
     /**
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheDiskUsageUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheDiskUsageUnitTest.java
index 3117f25..d4aab45 100644
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheDiskUsageUnitTest.java
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheDiskUsageUnitTest.java
@@ -20,6 +20,7 @@
  */
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -29,17 +30,16 @@
 import org.apache.commons.jcs3.access.CacheAccess;
 import org.apache.commons.jcs3.access.exception.CacheException;
 import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCache;
-import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
 import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
 import org.apache.commons.jcs3.engine.CacheElement;
 import org.apache.commons.jcs3.engine.CacheStatus;
 import org.apache.commons.jcs3.engine.CompositeCacheAttributes;
 import org.apache.commons.jcs3.engine.ElementAttributes;
 import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheType.CacheType;
 import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
 import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
 import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
-import org.apache.commons.jcs3.engine.behavior.ICacheType.CacheType;
 import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
 import org.apache.commons.jcs3.engine.stats.behavior.IStats;
 
@@ -107,11 +107,7 @@
 
         final MockAuxCache<String, String> mock = new MockAuxCache<>();
         mock.cacheType = CacheType.DISK_CACHE;
-
-        @SuppressWarnings("unchecked")
-        final
-        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
-        cache.setAuxCaches( auxArray );
+        cache.setAuxCaches(Arrays.asList(mock));
 
         final ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
 
@@ -140,11 +136,7 @@
 
         final MockAuxCache<String, String> mock = new MockAuxCache<>();
         mock.cacheType = CacheType.DISK_CACHE;
-
-        @SuppressWarnings("unchecked")
-        final
-        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
-        cache.setAuxCaches( auxArray );
+        cache.setAuxCaches(Arrays.asList(mock));
 
         final ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
 
@@ -177,11 +169,7 @@
 
         final MockAuxCache<String, String> mock = new MockAuxCache<>();
         mock.cacheType = CacheType.DISK_CACHE;
-
-        @SuppressWarnings("unchecked")
-        final
-        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
-        cache.setAuxCaches( auxArray );
+        cache.setAuxCaches(Arrays.asList(mock));
 
         final ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
 
@@ -216,11 +204,7 @@
 
         final MockAuxCache<String, String> mock = new MockAuxCache<>();
         mock.cacheType = CacheType.DISK_CACHE;
-
-        @SuppressWarnings("unchecked")
-        final
-        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
-        cache.setAuxCaches( auxArray );
+        cache.setAuxCaches(Arrays.asList(mock));
 
         final ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
 
@@ -255,11 +239,7 @@
 
         final MockAuxCache<String, String> mock = new MockAuxCache<>();
         mock.cacheType = CacheType.DISK_CACHE;
-
-        @SuppressWarnings("unchecked")
-        final
-        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock };
-        cache.setAuxCaches( auxArray );
+        cache.setAuxCaches(Arrays.asList(mock));
 
         final ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
 
@@ -295,11 +275,7 @@
 
         final MockAuxCache<String, String> mockLateral = new MockAuxCache<>();
         mockLateral.cacheType = CacheType.LATERAL_CACHE;
-
-        @SuppressWarnings("unchecked")
-        final
-        AuxiliaryCache<String, String>[] auxArray = new AuxiliaryCache[] { mock, mockLateral };
-        cache.setAuxCaches( auxArray );
+        cache.setAuxCaches(Arrays.asList(mock, mockLateral));
 
         final ICacheElement<String, String> inputElement = new CacheElement<>( CACHE_NAME, "key", "value" );
 
diff --git a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheUnitTest.java b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheUnitTest.java
index 78931e2..6da9fd8 100644
--- a/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheUnitTest.java
+++ b/commons-jcs-core/src/test/java/org/apache/commons/jcs3/engine/control/CompositeCacheUnitTest.java
@@ -1,5 +1,19 @@
 package org.apache.commons.jcs3.engine.control;
 
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+
+import org.apache.commons.jcs3.auxiliary.MockAuxiliaryCache;
+import org.apache.commons.jcs3.engine.CacheElement;
+import org.apache.commons.jcs3.engine.CompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.ElementAttributes;
+import org.apache.commons.jcs3.engine.behavior.ICacheElement;
+import org.apache.commons.jcs3.engine.behavior.ICacheType.CacheType;
+import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
+import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
+import org.apache.commons.jcs3.engine.memory.MockMemoryCache;
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -21,19 +35,6 @@
 
 import junit.framework.TestCase;
 
-import org.apache.commons.jcs3.auxiliary.MockAuxiliaryCache;
-import org.apache.commons.jcs3.engine.memory.MockMemoryCache;
-import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
-import org.apache.commons.jcs3.engine.CacheElement;
-import org.apache.commons.jcs3.engine.CompositeCacheAttributes;
-import org.apache.commons.jcs3.engine.ElementAttributes;
-import org.apache.commons.jcs3.engine.behavior.ICacheElement;
-import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
-import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
-import org.apache.commons.jcs3.engine.behavior.ICacheType.CacheType;
-import java.io.IOException;
-import java.util.Map;
-
 /**
  * Tests that directly engage the composite cache.
  * <p>
@@ -63,10 +64,7 @@
 
         final MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
         diskMock.cacheType = CacheType.DISK_CACHE;
-        @SuppressWarnings("unchecked")
-        final
-        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
-        cache.setAuxCaches( aux );
+        cache.setAuxCaches(Arrays.asList(diskMock));
 
         // DO WORK
         final int numToInsert = 10;
@@ -104,10 +102,7 @@
 
         final MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
         diskMock.cacheType = CacheType.REMOTE_CACHE;
-        @SuppressWarnings("unchecked")
-        final
-        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
-        cache.setAuxCaches( aux );
+        cache.setAuxCaches(Arrays.asList(diskMock));
 
         // DO WORK
         final int numToInsert = 10;
@@ -148,10 +143,7 @@
 
         final MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
         diskMock.cacheType = CacheType.DISK_CACHE;
-        @SuppressWarnings("unchecked")
-        final
-        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
-        cache.setAuxCaches( aux );
+        cache.setAuxCaches(Arrays.asList(diskMock));
 
         // DO WORK
         final int numToInsertPrefix1 = 10;
@@ -201,10 +193,7 @@
 
         final MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
         diskMock.cacheType = CacheType.DISK_CACHE;
-        @SuppressWarnings("unchecked")
-        final
-        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
-        cache.setAuxCaches( aux );
+        cache.setAuxCaches(Arrays.asList(diskMock));
 
         // DO WORK
         cache.getMatching( "junk" );
@@ -236,10 +225,7 @@
 
         final MockAuxiliaryCache<String, Integer> diskMock = new MockAuxiliaryCache<>();
         diskMock.cacheType = CacheType.REMOTE_CACHE;
-        @SuppressWarnings("unchecked")
-        final
-        AuxiliaryCache<String, Integer>[] aux = new AuxiliaryCache[] { diskMock };
-        cache.setAuxCaches( aux );
+        cache.setAuxCaches(Arrays.asList(diskMock));
 
         // DO WORK
         cache.getMatching( "junk" );