View Javadoc
1   /*
2    * Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 2.1 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this program; if not, write to the Free Software
16   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17   *
18   * Additionally licensed with:
19   *
20   * Licensed under the Apache License, Version 2.0 (the "License");
21   * you may not use this file except in compliance with the License.
22   * You may obtain a copy of the License at
23   *
24   *      http://www.apache.org/licenses/LICENSE-2.0
25   *
26   * Unless required by applicable law or agreed to in writing, software
27   * distributed under the License is distributed on an "AS IS" BASIS,
28   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29   * See the License for the specific language governing permissions and
30   * limitations under the License.
31   */
32  package org.spf4j.concurrent;
33  
34  import com.google.common.cache.CacheLoader;
35  import com.google.common.cache.CacheStats;
36  import com.google.common.cache.LoadingCache;
37  import com.google.common.collect.ImmutableMap;
38  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
39  import java.util.Collection;
40  import java.util.Comparator;
41  import java.util.Map;
42  import java.util.Set;
43  import java.util.concurrent.Callable;
44  import java.util.concurrent.ConcurrentHashMap;
45  import java.util.concurrent.ConcurrentMap;
46  import java.util.concurrent.ConcurrentSkipListMap;
47  import java.util.concurrent.ExecutionException;
48  import javax.annotation.Nullable;
49  import javax.annotation.ParametersAreNonnullByDefault;
50  import org.spf4j.base.Callables;
51  import org.spf4j.base.UncheckedExecutionException;
52  
53  /**
54   *
55   * custom build high performance implementation for a unbounded guava cache: UnboundedLoadingCache is implemented with
56   * JDK concurrent map UnboundedLoadingCache2 is using the JDK 1.8 computing map functionality, but benchmarks show worse
57   * performance.
58   *
59   * Benchmark Mode Cnt Score Error Units CacheBenchmark.guavaCache thrpt 15 29011674.275 ± 710672.413 ops/s
60   * CacheBenchmark.spf4j2Cache thrpt 15 30567248.015 ± 807965.535 ops/s CacheBenchmark.spf4jCache thrpt 15 37961593.882 ±
61   * 1136244.254 ops/s CacheBenchmark.spf4jRacyCache thrpt 15 37553655.751 ± 855349.501 ops/s
62   *
63   * @author zoly
64   */
65  @ParametersAreNonnullByDefault
66  public final class UnboundedLoadingCache<K, V> implements LoadingCache<K, V> {
67  
68    private final ConcurrentMap<K, Callable<? extends V>> map;
69  
70    private final CacheLoader<K, V> loader;
71  
72    public UnboundedLoadingCache(final int initialSize, final CacheLoader<K, V> loader) {
73      this(initialSize, 8, loader);
74    }
75  
76    public UnboundedLoadingCache(final int initialSize, final int concurrency, final CacheLoader<K, V> loader) {
77      this.map = new ConcurrentHashMap<>(
78              initialSize, 0.75f, concurrency);
79      this.loader = loader;
80    }
81  
82    /**
83     * Will use a ConcurrentSkipListMap to store the underlying data.
84     * @param comparator
85     * @param loader
86     */
87    public UnboundedLoadingCache(final Comparator<? super K> comparator, final CacheLoader<K, V> loader) {
88      this.map = new ConcurrentSkipListMap<>(comparator);
89      this.loader = loader;
90    }
91  
92  
93    @Override
94    public V get(final K key) throws ExecutionException {
95      Callable<? extends V> existingValHolder = map.get(key);
96      if (existingValHolder == null) {
97        Callable<? extends V> newHolder = Callables.memorized(() -> loader.load(key));
98        existingValHolder = map.putIfAbsent(key, newHolder);
99        if (existingValHolder == null) {
100         existingValHolder = newHolder;
101       }
102     }
103     try {
104       return existingValHolder.call();
105     } catch (Exception ex) {
106       throw new ExecutionException(ex);
107     }
108 
109   }
110 
111   @Override
112   public V getUnchecked(final K key) {
113     try {
114       return get(key);
115     } catch (ExecutionException ex) {
116       throw new UncheckedExecutionException(ex);
117     }
118   }
119 
120   @Override
121   @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED")
122   public ImmutableMap<K, V> getAll(final Iterable<? extends K> keys) throws ExecutionException {
123     ImmutableMap.Builder<K, V> builder = ImmutableMap.builder();
124     for (K key : keys) {
125       builder.put(key, get(key));
126     }
127     return builder.build();
128   }
129 
130   @Override
131   @SuppressFBWarnings("NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE")
132   public V apply(final K key) {
133     return getUnchecked(key);
134   }
135 
136   @Override
137   public void refresh(final K key) {
138     Callable<? extends V> newHolder = Callables.memorized(new Callable<V>() {
139       @Override
140       public V call() throws Exception {
141         return loader.load(key);
142       }
143     });
144     map.put(key, newHolder);
145   }
146 
147   @Override
148   public ConcurrentMap<K, V> asMap() {
149     return new MapView();
150   }
151 
152   @Override
153   @Nullable
154   public V getIfPresent(final Object key) {
155     Callable<? extends V> existingValHolder = map.get(key);
156     if (existingValHolder != null) {
157       try {
158         return existingValHolder.call();
159       } catch (Exception ex) {
160         throw new UncheckedExecutionException(ex);
161       }
162     } else {
163       return null;
164     }
165   }
166 
167   @Override
168   public V get(final K key, final Callable<? extends V> valueLoader) throws ExecutionException {
169     Callable<? extends V> existingValHolder = map.get(key);
170     if (existingValHolder == null) {
171       Callable<? extends V> newHolder = Callables.memorized(valueLoader);
172       existingValHolder = map.putIfAbsent(key, newHolder);
173       if (existingValHolder == null) {
174         existingValHolder = newHolder;
175       }
176     }
177     try {
178       return existingValHolder.call();
179     } catch (Exception ex) {
180       throw new ExecutionException(ex);
181     }
182   }
183 
184   @Override
185   @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED")
186   public ImmutableMap<K, V> getAllPresent(final Iterable<?> keys) {
187     ImmutableMap.Builder<K, V> builder = ImmutableMap.builder();
188     for (K key : (Iterable<K>) keys) {
189       V val = getIfPresent(key);
190       if (val != null) {
191         builder.put(key, val);
192       }
193     }
194     return builder.build();
195   }
196 
197   @Override
198   public void put(final K key, final V value) {
199     map.put(key, Callables.constant(value));
200   }
201 
202   @Override
203   public void putAll(final Map<? extends K, ? extends V> m) {
204     for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
205       put(entry.getKey(), entry.getValue());
206     }
207   }
208 
209   @Override
210   public void invalidate(final Object key) {
211     map.remove(key);
212   }
213 
214   @Override
215   public void invalidateAll(final Iterable<?> keys) {
216     for (K key : (Iterable<K>) keys) {
217       invalidate(key);
218     }
219   }
220 
221   @Override
222   public void invalidateAll() {
223     map.clear();
224   }
225 
226   @Override
227   public long size() {
228     return map.size();
229   }
230 
231   @Override
232   public CacheStats stats() {
233     throw new UnsupportedOperationException("Not supported");
234   }
235 
236   @Override
237   @SuppressFBWarnings("NM_CONFUSING")
238   public void cleanUp() {
239     map.clear();
240   }
241 
242   public Set<K> getKeysLoaded() {
243     return map.keySet();
244   }
245 
246   @Override
247   public String toString() {
248     return "UnboundedLoadingCache{" + "map=" + map + ", loader=" + loader + '}';
249   }
250 
251   private class MapView implements ConcurrentMap<K, V> {
252 
253     @Override
254     public V putIfAbsent(final K key, final V value) {
255       Callable<? extends V> result = map.putIfAbsent(key, Callables.constant(value));
256       try {
257         return result == null ? null : result.call();
258       } catch (Exception ex) {
259         throw new UncheckedExecutionException(ex);
260       }
261     }
262 
263     @Override
264     public boolean remove(final Object key, final Object value) {
265       throw new UnsupportedOperationException();
266     }
267 
268     @Override
269     public boolean replace(final K key, final V oldValue, final V newValue) {
270       throw new UnsupportedOperationException();
271     }
272 
273     @Override
274     public V replace(final K key, final V value) {
275       throw new UnsupportedOperationException();
276     }
277 
278     @Override
279     public int size() {
280       return map.size();
281     }
282 
283     @Override
284     public boolean isEmpty() {
285       return map.isEmpty();
286     }
287 
288     @Override
289     public boolean containsKey(final Object key) {
290       return map.containsKey(key);
291     }
292 
293     @Override
294     public boolean containsValue(final Object value) {
295       throw new UnsupportedOperationException();
296     }
297 
298     @Override
299     public V get(final Object key) {
300       Callable<? extends V> result = map.get(key);
301       try {
302         return result == null ? null : result.call();
303       } catch (Exception ex) {
304         throw new UncheckedExecutionException(ex);
305       }
306     }
307 
308     @Override
309     public V put(final K key, final V value) {
310       Callable<? extends V> result = map.put(key, Callables.constant(value));
311       try {
312         return result == null ? null : result.call();
313       } catch (Exception ex) {
314         throw new UncheckedExecutionException(ex);
315       }
316     }
317 
318     @Override
319     public V remove(final Object key) {
320       Callable<? extends V> result = map.remove(key);
321       try {
322         return result == null ? null : result.call();
323       } catch (Exception ex) {
324         throw new UncheckedExecutionException(ex);
325       }
326     }
327 
328     @Override
329     public void putAll(final Map<? extends K, ? extends V> m) {
330       for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
331         map.put(entry.getKey(), entry::getValue);
332       }
333     }
334 
335     @Override
336     public void clear() {
337       map.clear();
338     }
339 
340     @Override
341     public Set<K> keySet() {
342       return map.keySet();
343     }
344 
345     @Override
346     public Collection<V> values() {
347       throw new UnsupportedOperationException();
348     }
349 
350     @Override
351     public Set<Map.Entry<K, V>> entrySet() {
352       throw new UnsupportedOperationException();
353     }
354   }
355 
356 }