View Javadoc
1   /*
2    * Copyright (C) 2011 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.util.concurrent;
18  
19  import static com.google.common.base.Preconditions.checkNotNull;
20  
21  import com.google.common.annotations.Beta;
22  import com.google.common.annotations.GwtCompatible;
23  import com.google.errorprone.annotations.CanIgnoreReturnValue;
24  import java.io.Serializable;
25  import java.util.Collections;
26  import java.util.Map;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.atomic.AtomicBoolean;
29  import java.util.concurrent.atomic.AtomicLong;
30  import java.util.function.LongBinaryOperator;
31  import java.util.function.LongUnaryOperator;
32  
33  /**
34   * A map containing {@code long} values that can be atomically updated. While writes to a
35   * traditional {@code Map} rely on {@code put(K, V)}, the typical mechanism for writing to this map
36   * is {@code addAndGet(K, long)}, which adds a {@code long} to the value currently associated with
37   * {@code K}. If a key has not yet been associated with a value, its implicit value is zero.
38   *
39   * <p>Most methods in this class treat absent values and zero values identically, as individually
40   * documented. Exceptions to this are {@link #containsKey}, {@link #size}, {@link #isEmpty},
41   * {@link #asMap}, and {@link #toString}.
42   *
43   * <p>Instances of this class may be used by multiple threads concurrently. All operations are
44   * atomic unless otherwise noted.
45   *
46   * <p><b>Note:</b> If your values are always positive and less than 2^31, you may wish to use a
47   * {@link com.google.common.collect.Multiset} such as
48   * {@link com.google.common.collect.ConcurrentHashMultiset} instead.
49   *
50   * <b>Warning:</b> Unlike {@code Multiset}, entries whose values are zero are not automatically
51   * removed from the map. Instead they must be removed manually with {@link #removeAllZeros}.
52   *
53   * @author Charles Fry
54   * @since 11.0
55   */
56  @GwtCompatible
57  public final class AtomicLongMap<K> implements Serializable {
58    private final ConcurrentHashMap<K, Long> map;
59  
60    private AtomicLongMap(ConcurrentHashMap<K, Long> map) {
61      this.map = checkNotNull(map);
62    }
63  
64    /**
65     * Creates an {@code AtomicLongMap}.
66     */
67    public static <K> AtomicLongMap<K> create() {
68      return new AtomicLongMap<K>(new ConcurrentHashMap<>());
69    }
70  
71    /**
72     * Creates an {@code AtomicLongMap} with the same mappings as the specified {@code Map}.
73     */
74    public static <K> AtomicLongMap<K> create(Map<? extends K, ? extends Long> m) {
75      AtomicLongMap<K> result = create();
76      result.putAll(m);
77      return result;
78    }
79  
80    /**
81     * Returns the value associated with {@code key}, or zero if there is no value associated with
82     * {@code key}.
83     */
84    public long get(K key) {
85      return map.getOrDefault(key, 0L);
86    }
87  
88    /**
89     * Increments by one the value currently associated with {@code key}, and returns the new value.
90     */
91    @CanIgnoreReturnValue
92    public long incrementAndGet(K key) {
93      return addAndGet(key, 1);
94    }
95  
96    /**
97     * Decrements by one the value currently associated with {@code key}, and returns the new value.
98     */
99    @CanIgnoreReturnValue
100   public long decrementAndGet(K key) {
101     return addAndGet(key, -1);
102   }
103 
104   /**
105    * Adds {@code delta} to the value currently associated with {@code key}, and returns the new
106    * value.
107    */
108   @CanIgnoreReturnValue
109   public long addAndGet(K key, long delta) {
110     return accumulateAndGet(key, delta, Long::sum);
111   }
112 
113   /**
114    * Increments by one the value currently associated with {@code key}, and returns the old value.
115    */
116   @CanIgnoreReturnValue
117   public long getAndIncrement(K key) {
118     return getAndAdd(key, 1);
119   }
120 
121   /**
122    * Decrements by one the value currently associated with {@code key}, and returns the old value.
123    */
124   @CanIgnoreReturnValue
125   public long getAndDecrement(K key) {
126     return getAndAdd(key, -1);
127   }
128 
129   /**
130    * Adds {@code delta} to the value currently associated with {@code key}, and returns the old
131    * value.
132    */
133   @CanIgnoreReturnValue
134   public long getAndAdd(K key, long delta) {
135     return getAndAccumulate(key, delta, Long::sum);
136   }
137 
138   /**
139    * Updates the value currently associated with {@code key} with the specified function,
140    * and returns the new value.  If there is not currently a value associated with {@code key},
141    * the function is applied to {@code 0L}.
142    *
143    * @since 21.0
144    */
145   @CanIgnoreReturnValue
146   public long updateAndGet(K key, LongUnaryOperator updaterFunction) {
147     checkNotNull(updaterFunction);
148     return map.compute(
149         key, (k, value) -> updaterFunction.applyAsLong((value == null) ? 0L : value.longValue()));
150   }
151 
152   /**
153    * Updates the value currently associated with {@code key} with the specified function,
154    * and returns the old value.  If there is not currently a value associated with {@code key},
155    * the function is applied to {@code 0L}.
156    *
157    * @since 21.0
158    */
159   @CanIgnoreReturnValue
160   public long getAndUpdate(K key, LongUnaryOperator updaterFunction) {
161     checkNotNull(updaterFunction);
162     AtomicLong holder = new AtomicLong();
163     map.compute(
164         key,
165         (k, value) -> {
166           long oldValue = (value == null) ? 0L : value.longValue();
167           holder.set(oldValue);
168           return updaterFunction.applyAsLong(oldValue);
169         });
170     return holder.get();
171   }
172 
173   /**
174    * Updates the value currently associated with {@code key} by combining it with {@code x}
175    * via the specified accumulator function, returning the new value.  The previous value
176    * associated with {@code key} (or zero, if there is none) is passed as the first argument
177    * to {@code accumulatorFunction}, and {@code x} is passed as the second argument.
178    *
179    * @since 21.0
180    */
181   @CanIgnoreReturnValue
182   public long accumulateAndGet(K key, long x, LongBinaryOperator accumulatorFunction) {
183     checkNotNull(accumulatorFunction);
184     return updateAndGet(key, oldValue -> accumulatorFunction.applyAsLong(oldValue, x));
185   }
186 
187   /**
188    * Updates the value currently associated with {@code key} by combining it with {@code x}
189    * via the specified accumulator function, returning the old value.  The previous value
190    * associated with {@code key} (or zero, if there is none) is passed as the first argument
191    * to {@code accumulatorFunction}, and {@code x} is passed as the second argument.
192    *
193    * @since 21.0
194    */
195   @CanIgnoreReturnValue
196   public long getAndAccumulate(K key, long x, LongBinaryOperator accumulatorFunction) {
197     checkNotNull(accumulatorFunction);
198     return getAndUpdate(key, oldValue -> accumulatorFunction.applyAsLong(oldValue, x));
199   }
200 
201   /**
202    * Associates {@code newValue} with {@code key} in this map, and returns the value previously
203    * associated with {@code key}, or zero if there was no such value.
204    */
205   @CanIgnoreReturnValue
206   public long put(K key, long newValue) {
207     return getAndUpdate(key, x -> newValue);
208   }
209 
210   /**
211    * Copies all of the mappings from the specified map to this map. The effect of this call is
212    * equivalent to that of calling {@code put(k, v)} on this map once for each mapping from key
213    * {@code k} to value {@code v} in the specified map. The behavior of this operation is undefined
214    * if the specified map is modified while the operation is in progress.
215    */
216   public void putAll(Map<? extends K, ? extends Long> m) {
217     m.forEach(this::put);
218   }
219 
220   /**
221    * Removes and returns the value associated with {@code key}. If {@code key} is not
222    * in the map, this method has no effect and returns zero.
223    */
224   @CanIgnoreReturnValue
225   public long remove(K key) {
226     Long result = map.remove(key);
227     return (result == null) ? 0L : result.longValue();
228   }
229 
230   /**
231    * Atomically remove {@code key} from the map iff its associated value is 0.
232    *
233    * @since 20.0
234    */
235   @Beta
236   @CanIgnoreReturnValue
237   public boolean removeIfZero(K key) {
238     return remove(key, 0);
239   }
240 
241   /**
242    * Removes all mappings from this map whose values are zero.
243    *
244    * <p>This method is not atomic: the map may be visible in intermediate states, where some
245    * of the zero values have been removed and others have not.
246    */
247   public void removeAllZeros() {
248     map.values().removeIf(x -> x == 0);
249   }
250 
251   /**
252    * Returns the sum of all values in this map.
253    *
254    * <p>This method is not atomic: the sum may or may not include other concurrent operations.
255    */
256   public long sum() {
257     return map.values().stream().mapToLong(Long::longValue).sum();
258   }
259 
260   private transient Map<K, Long> asMap;
261 
262   /**
263    * Returns a live, read-only view of the map backing this {@code AtomicLongMap}.
264    */
265   public Map<K, Long> asMap() {
266     Map<K, Long> result = asMap;
267     return (result == null) ? asMap = createAsMap() : result;
268   }
269 
270   private Map<K, Long> createAsMap() {
271     return Collections.unmodifiableMap(map);
272   }
273 
274   /**
275    * Returns true if this map contains a mapping for the specified key.
276    */
277   public boolean containsKey(Object key) {
278     return map.containsKey(key);
279   }
280 
281   /**
282    * Returns the number of key-value mappings in this map. If the map contains more than
283    * {@code Integer.MAX_VALUE} elements, returns {@code Integer.MAX_VALUE}.
284    */
285   public int size() {
286     return map.size();
287   }
288 
289   /**
290    * Returns {@code true} if this map contains no key-value mappings.
291    */
292   public boolean isEmpty() {
293     return map.isEmpty();
294   }
295 
296   /**
297    * Removes all of the mappings from this map. The map will be empty after this call returns.
298    *
299    * <p>This method is not atomic: the map may not be empty after returning if there were concurrent
300    * writes.
301    */
302   public void clear() {
303     map.clear();
304   }
305 
306   @Override
307   public String toString() {
308     return map.toString();
309   }
310 
311   /**
312    * If {@code key} is not already associated with a value or if {@code key} is associated with
313    * zero, associate it with {@code newValue}. Returns the previous value associated with
314    * {@code key}, or zero if there was no mapping for {@code key}.
315    */
316   long putIfAbsent(K key, long newValue) {
317     AtomicBoolean noValue = new AtomicBoolean(false);
318     Long result =
319         map.compute(
320             key,
321             (k, oldValue) -> {
322               if (oldValue == null || oldValue == 0) {
323                 noValue.set(true);
324                 return newValue;
325               } else {
326                 return oldValue;
327               }
328             });
329     return noValue.get() ? 0L : result.longValue();
330   }
331 
332   /**
333    * If {@code (key, expectedOldValue)} is currently in the map, this method replaces
334    * {@code expectedOldValue} with {@code newValue} and returns true; otherwise, this method
335    * returns false.
336    *
337    * <p>If {@code expectedOldValue} is zero, this method will succeed if {@code (key, zero)}
338    * is currently in the map, or if {@code key} is not in the map at all.
339    */
340   boolean replace(K key, long expectedOldValue, long newValue) {
341     if (expectedOldValue == 0L) {
342       return putIfAbsent(key, newValue) == 0L;
343     } else {
344       return map.replace(key, expectedOldValue, newValue);
345     }
346   }
347 
348   /**
349    * If {@code (key, value)} is currently in the map, this method removes it and returns
350    * true; otherwise, this method returns false.
351    */
352   boolean remove(K key, long value) {
353     return map.remove(key, value);
354   }
355 }