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     * Returns a power of two for the given target capacity.
65     *
66     * @param cap capacity
67     * @return the smallest power of 2 greater or equal to argument
68     */
69    private static int closestPowerOf2(final int cap) {
70      return cap > 1 ? Integer.highestOneBit(cap - 1) << 1 : 1;
71    }
72  
73    /**
74     * The sub-maps array.
75     */
76    private final Map<K, V>[] maps;
77  
78    /**
79     * The unique simple constructor.
80     *
81     * @param capacity the overall map capacity
82     */
83    SpreadMap(final int capacity) {
84      final int spread = closestPowerOf2(Runtime.getRuntime().availableProcessors());
85      maps = new Map[spread];
86      final int mapCapacity = (capacity + spread + 1) / spread;
87      for (int m = 0; m < spread; ++m) {
88        maps[m] = SoftCache.createSynchronizedLinkedHashMap(mapCapacity);
89      }
90    }
91  
92    @Override
93    public void clear() {
94      for (final Map<K, V> map : maps) {
95        map.clear();
96      }
97    }
98  
99    @Override
100   public Set<Entry<K, V>> entrySet() {
101     final Set<Map.Entry<K, V>> entries = new LinkedHashSet<>(size());
102     for (final Map<K, V> map : maps) {
103       synchronized (map) {
104         entries.addAll(map.entrySet());
105       }
106     }
107     return entries;
108   }
109 
110   @Override
111   public V get(final Object key) {
112     return getMap(key).get(key);
113   }
114 
115   /**
116    * Gets the map storing a given key.
117    *
118    * @param key the key
119    * @return the map
120    */
121   private final Map<K, V> getMap(final Object key) {
122     int h = key.hashCode();
123     h ^= h >>> 16;
124     // length is a power of 2, length - 1 is the mask of its modulo:
125     // length = 4, length - 1 = 3 = 11b : x % 4 <=> x & 3
126     return maps[h & maps.length - 1];
127   }
128 
129   @Override
130   public V put(final K key, final V value) {
131     return getMap(key).put(key, value);
132   }
133 
134   @Override
135   public V remove(final Object key) {
136     return getMap(key).remove(key);
137   }
138 
139   @Override
140   public int size() {
141     int size = 0;
142     for (final Map<K, V> map : maps) {
143       size += map.size();
144     }
145     return size;
146   }
147 }