View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jexl3;
19  
20  import java.util.AbstractMap;
21  import java.util.LinkedHashSet;
22  import java.util.Map;
23  import java.util.Set;
24  
25  import org.apache.commons.jexl3.internal.SoftCache;
26  
27  /**
28   * Creates a cache using an array of synchronized LinkedHashMap as backing store to spread contention.
29   * <p>Just meant as a contention reducing mechanism for cache tests.</p>
30   *
31   * @param <K> the cached element&quot;s key
32   * @param <V> the cached element&quot;s value
33   * @return the cache instance
34   */
35  public class SpreadCache<K, V> extends SoftCache<K, V> {
36  
37    /**
38     * Creates a new instance of a Spread cache.
39     *
40     * @param theSize the cache size
41     */
42    public SpreadCache(final int theSize) {
43      super(theSize);
44    }
45  
46    @Override
47    public <K, V> Map<K, V> createMap(final int cacheSize) {
48      return new SpreadMap<>(cacheSize);
49    }
50  }
51  
52  /**
53   * A map backed by an array of LinkedHashMap.
54   * <p>This implementation is really tailored to serve the cache methods and no-other. It foregoes being efficient
55   * for methods that do not serve this purpose.</p>
56   * <p>We spread the map capacity over a number of synchronized sub-maps, the number being the number of
57   * available processors. This is meant to spread the contention at the cost of a relaxed LRU.</p>
58   *
59   * @param <K>
60   * @param <V>
61   */
62  class SpreadMap<K, V> extends AbstractMap<K, V> {
63  
64    /**
65     * Returns a power of two for the given target capacity.
66     *
67     * @param cap capacity
68     * @return the smallest power of 2 greater or equal to argument
69     */
70    private static int closestPowerOf2(final int cap) {
71      return cap > 1 ? Integer.highestOneBit(cap - 1) << 1 : 1;
72    }
73  
74    /**
75     * The sub-maps array.
76     */
77    private final Map<K, V>[] maps;
78  
79    /**
80     * The unique simple constructor.
81     *
82     * @param capacity the overall map capacity
83     */
84    SpreadMap(final int capacity) {
85      final int spread = closestPowerOf2(Runtime.getRuntime().availableProcessors());
86      maps = new Map[spread];
87      final int mapCapacity = (capacity + spread + 1) / spread;
88      for (int m = 0; m < spread; ++m) {
89        maps[m] = SoftCache.createSynchronizedLinkedHashMap(mapCapacity);
90      }
91    }
92  
93    @Override
94    public void clear() {
95      for (final Map<K, V> map : maps) {
96        map.clear();
97      }
98    }
99  
100   @Override
101   public Set<Entry<K, V>> entrySet() {
102     final Set<Map.Entry<K, V>> entries = new LinkedHashSet<>(size());
103     for (final Map<K, V> map : maps) {
104       synchronized (map) {
105         entries.addAll(map.entrySet());
106       }
107     }
108     return entries;
109   }
110 
111   @Override
112   public V get(final Object key) {
113     return getMap(key).get(key);
114   }
115 
116   /**
117    * Gets the map storing a given key.
118    *
119    * @param key the key
120    * @return the map
121    */
122   private final Map<K, V> getMap(final Object key) {
123     int h = key.hashCode();
124     h ^= h >>> 16;
125     // length is a power of 2, length - 1 is the mask of its modulo:
126     // length = 4, length - 1 = 3 = 11b : x % 4 <=> x & 3
127     return maps[h & maps.length - 1];
128   }
129 
130   @Override
131   public V put(final K key, final V value) {
132     return getMap(key).put(key, value);
133   }
134 
135   @Override
136   public V remove(final Object key) {
137     return getMap(key).remove(key);
138   }
139 
140   @Override
141   public int size() {
142     int size = 0;
143     for (final Map<K, V> map : maps) {
144       size += map.size();
145     }
146     return size;
147   }
148 }