001/*
002 * Copyright (C) 2011 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.util.concurrent;
018
019import static com.google.common.base.Preconditions.checkNotNull;
020
021import com.google.common.annotations.Beta;
022import com.google.common.annotations.GwtCompatible;
023import com.google.errorprone.annotations.CanIgnoreReturnValue;
024import java.io.Serializable;
025import java.util.Collections;
026import java.util.Map;
027import java.util.concurrent.ConcurrentHashMap;
028import java.util.concurrent.atomic.AtomicBoolean;
029import java.util.concurrent.atomic.AtomicLong;
030import java.util.function.LongBinaryOperator;
031import java.util.function.LongUnaryOperator;
032
033/**
034 * A map containing {@code long} values that can be atomically updated. While writes to a
035 * traditional {@code Map} rely on {@code put(K, V)}, the typical mechanism for writing to this map
036 * is {@code addAndGet(K, long)}, which adds a {@code long} to the value currently associated with
037 * {@code K}. If a key has not yet been associated with a value, its implicit value is zero.
038 *
039 * <p>Most methods in this class treat absent values and zero values identically, as individually
040 * documented. Exceptions to this are {@link #containsKey}, {@link #size}, {@link #isEmpty}, {@link
041 * #asMap}, and {@link #toString}.
042 *
043 * <p>Instances of this class may be used by multiple threads concurrently. All operations are
044 * atomic unless otherwise noted.
045 *
046 * <p><b>Note:</b> If your values are always positive and less than 2^31, you may wish to use a
047 * {@link com.google.common.collect.Multiset} such as {@link
048 * com.google.common.collect.ConcurrentHashMultiset} instead.
049 *
050 * <p><b>Warning:</b> Unlike {@code Multiset}, entries whose values are zero are not automatically
051 * removed from the map. Instead they must be removed manually with {@link #removeAllZeros}.
052 *
053 * @author Charles Fry
054 * @since 11.0
055 */
056@GwtCompatible
057public final class AtomicLongMap<K> implements Serializable {
058  private final ConcurrentHashMap<K, Long> map;
059
060  private AtomicLongMap(ConcurrentHashMap<K, Long> map) {
061    this.map = checkNotNull(map);
062  }
063
064  /** Creates an {@code AtomicLongMap}. */
065  public static <K> AtomicLongMap<K> create() {
066    return new AtomicLongMap<K>(new ConcurrentHashMap<>());
067  }
068
069  /** Creates an {@code AtomicLongMap} with the same mappings as the specified {@code Map}. */
070  public static <K> AtomicLongMap<K> create(Map<? extends K, ? extends Long> m) {
071    AtomicLongMap<K> result = create();
072    result.putAll(m);
073    return result;
074  }
075
076  /**
077   * Returns the value associated with {@code key}, or zero if there is no value associated with
078   * {@code key}.
079   */
080  public long get(K key) {
081    return map.getOrDefault(key, 0L);
082  }
083
084  /**
085   * Increments by one the value currently associated with {@code key}, and returns the new value.
086   */
087  @CanIgnoreReturnValue
088  public long incrementAndGet(K key) {
089    return addAndGet(key, 1);
090  }
091
092  /**
093   * Decrements by one the value currently associated with {@code key}, and returns the new value.
094   */
095  @CanIgnoreReturnValue
096  public long decrementAndGet(K key) {
097    return addAndGet(key, -1);
098  }
099
100  /**
101   * Adds {@code delta} to the value currently associated with {@code key}, and returns the new
102   * value.
103   */
104  @CanIgnoreReturnValue
105  public long addAndGet(K key, long delta) {
106    return accumulateAndGet(key, delta, Long::sum);
107  }
108
109  /**
110   * Increments by one the value currently associated with {@code key}, and returns the old value.
111   */
112  @CanIgnoreReturnValue
113  public long getAndIncrement(K key) {
114    return getAndAdd(key, 1);
115  }
116
117  /**
118   * Decrements by one the value currently associated with {@code key}, and returns the old value.
119   */
120  @CanIgnoreReturnValue
121  public long getAndDecrement(K key) {
122    return getAndAdd(key, -1);
123  }
124
125  /**
126   * Adds {@code delta} to the value currently associated with {@code key}, and returns the old
127   * value.
128   */
129  @CanIgnoreReturnValue
130  public long getAndAdd(K key, long delta) {
131    return getAndAccumulate(key, delta, Long::sum);
132  }
133
134  /**
135   * Updates the value currently associated with {@code key} with the specified function, and
136   * returns the new value. If there is not currently a value associated with {@code key}, the
137   * function is applied to {@code 0L}.
138   *
139   * @since 21.0
140   */
141  @CanIgnoreReturnValue
142  public long updateAndGet(K key, LongUnaryOperator updaterFunction) {
143    checkNotNull(updaterFunction);
144    return map.compute(
145        key, (k, value) -> updaterFunction.applyAsLong((value == null) ? 0L : value.longValue()));
146  }
147
148  /**
149   * Updates the value currently associated with {@code key} with the specified function, and
150   * returns the old value. If there is not currently a value associated with {@code key}, the
151   * function is applied to {@code 0L}.
152   *
153   * @since 21.0
154   */
155  @CanIgnoreReturnValue
156  public long getAndUpdate(K key, LongUnaryOperator updaterFunction) {
157    checkNotNull(updaterFunction);
158    AtomicLong holder = new AtomicLong();
159    map.compute(
160        key,
161        (k, value) -> {
162          long oldValue = (value == null) ? 0L : value.longValue();
163          holder.set(oldValue);
164          return updaterFunction.applyAsLong(oldValue);
165        });
166    return holder.get();
167  }
168
169  /**
170   * Updates the value currently associated with {@code key} by combining it with {@code x} via the
171   * specified accumulator function, returning the new value. The previous value associated with
172   * {@code key} (or zero, if there is none) is passed as the first argument to {@code
173   * accumulatorFunction}, and {@code x} is passed as the second argument.
174   *
175   * @since 21.0
176   */
177  @CanIgnoreReturnValue
178  public long accumulateAndGet(K key, long x, LongBinaryOperator accumulatorFunction) {
179    checkNotNull(accumulatorFunction);
180    return updateAndGet(key, oldValue -> accumulatorFunction.applyAsLong(oldValue, x));
181  }
182
183  /**
184   * Updates the value currently associated with {@code key} by combining it with {@code x} via the
185   * specified accumulator function, returning the old value. The previous value associated with
186   * {@code key} (or zero, if there is none) is passed as the first argument to {@code
187   * accumulatorFunction}, and {@code x} is passed as the second argument.
188   *
189   * @since 21.0
190   */
191  @CanIgnoreReturnValue
192  public long getAndAccumulate(K key, long x, LongBinaryOperator accumulatorFunction) {
193    checkNotNull(accumulatorFunction);
194    return getAndUpdate(key, oldValue -> accumulatorFunction.applyAsLong(oldValue, x));
195  }
196
197  /**
198   * Associates {@code newValue} with {@code key} in this map, and returns the value previously
199   * associated with {@code key}, or zero if there was no such value.
200   */
201  @CanIgnoreReturnValue
202  public long put(K key, long newValue) {
203    return getAndUpdate(key, x -> newValue);
204  }
205
206  /**
207   * Copies all of the mappings from the specified map to this map. The effect of this call is
208   * equivalent to that of calling {@code put(k, v)} on this map once for each mapping from key
209   * {@code k} to value {@code v} in the specified map. The behavior of this operation is undefined
210   * if the specified map is modified while the operation is in progress.
211   */
212  public void putAll(Map<? extends K, ? extends Long> m) {
213    m.forEach(this::put);
214  }
215
216  /**
217   * Removes and returns the value associated with {@code key}. If {@code key} is not in the map,
218   * this method has no effect and returns zero.
219   */
220  @CanIgnoreReturnValue
221  public long remove(K key) {
222    Long result = map.remove(key);
223    return (result == null) ? 0L : result.longValue();
224  }
225
226  /**
227   * Atomically remove {@code key} from the map iff its associated value is 0.
228   *
229   * @since 20.0
230   */
231  @Beta
232  @CanIgnoreReturnValue
233  public boolean removeIfZero(K key) {
234    return remove(key, 0);
235  }
236
237  /**
238   * Removes all mappings from this map whose values are zero.
239   *
240   * <p>This method is not atomic: the map may be visible in intermediate states, where some of the
241   * zero values have been removed and others have not.
242   */
243  public void removeAllZeros() {
244    map.values().removeIf(x -> x == 0);
245  }
246
247  /**
248   * Returns the sum of all values in this map.
249   *
250   * <p>This method is not atomic: the sum may or may not include other concurrent operations.
251   */
252  public long sum() {
253    return map.values().stream().mapToLong(Long::longValue).sum();
254  }
255
256  private transient Map<K, Long> asMap;
257
258  /** Returns a live, read-only view of the map backing this {@code AtomicLongMap}. */
259  public Map<K, Long> asMap() {
260    Map<K, Long> result = asMap;
261    return (result == null) ? asMap = createAsMap() : result;
262  }
263
264  private Map<K, Long> createAsMap() {
265    return Collections.unmodifiableMap(map);
266  }
267
268  /** Returns true if this map contains a mapping for the specified key. */
269  public boolean containsKey(Object key) {
270    return map.containsKey(key);
271  }
272
273  /**
274   * Returns the number of key-value mappings in this map. If the map contains more than {@code
275   * Integer.MAX_VALUE} elements, returns {@code Integer.MAX_VALUE}.
276   */
277  public int size() {
278    return map.size();
279  }
280
281  /** Returns {@code true} if this map contains no key-value mappings. */
282  public boolean isEmpty() {
283    return map.isEmpty();
284  }
285
286  /**
287   * Removes all of the mappings from this map. The map will be empty after this call returns.
288   *
289   * <p>This method is not atomic: the map may not be empty after returning if there were concurrent
290   * writes.
291   */
292  public void clear() {
293    map.clear();
294  }
295
296  @Override
297  public String toString() {
298    return map.toString();
299  }
300
301  /**
302   * If {@code key} is not already associated with a value or if {@code key} is associated with
303   * zero, associate it with {@code newValue}. Returns the previous value associated with {@code
304   * key}, or zero if there was no mapping for {@code key}.
305   */
306  long putIfAbsent(K key, long newValue) {
307    AtomicBoolean noValue = new AtomicBoolean(false);
308    Long result =
309        map.compute(
310            key,
311            (k, oldValue) -> {
312              if (oldValue == null || oldValue == 0) {
313                noValue.set(true);
314                return newValue;
315              } else {
316                return oldValue;
317              }
318            });
319    return noValue.get() ? 0L : result.longValue();
320  }
321
322  /**
323   * If {@code (key, expectedOldValue)} is currently in the map, this method replaces {@code
324   * expectedOldValue} with {@code newValue} and returns true; otherwise, this method returns false.
325   *
326   * <p>If {@code expectedOldValue} is zero, this method will succeed if {@code (key, zero)} is
327   * currently in the map, or if {@code key} is not in the map at all.
328   */
329  boolean replace(K key, long expectedOldValue, long newValue) {
330    if (expectedOldValue == 0L) {
331      return putIfAbsent(key, newValue) == 0L;
332    } else {
333      return map.replace(key, expectedOldValue, newValue);
334    }
335  }
336
337  /**
338   * If {@code (key, value)} is currently in the map, this method removes it and returns true;
339   * otherwise, this method returns false.
340   */
341  boolean remove(K key, long value) {
342    return map.remove(key, value);
343  }
344}