001/*
002 * Copyright (C) 2011 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 */
014
015package com.google.common.util.concurrent;
016
017import static com.google.common.base.Preconditions.checkNotNull;
018
019import com.google.common.annotations.Beta;
020import com.google.common.annotations.GwtCompatible;
021import com.google.common.base.Function;
022import com.google.common.collect.Maps;
023import com.google.errorprone.annotations.CanIgnoreReturnValue;
024import java.io.Serializable;
025import java.util.Collections;
026import java.util.Iterator;
027import java.util.Map;
028import java.util.Map.Entry;
029import java.util.concurrent.ConcurrentHashMap;
030import java.util.concurrent.atomic.AtomicLong;
031import org.checkerframework.checker.nullness.compatqual.MonotonicNonNullDecl;
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, AtomicLong> map;
059
060  private AtomicLongMap(ConcurrentHashMap<K, AtomicLong> 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<K, AtomicLong>());
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    AtomicLong atomic = map.get(key);
082    return atomic == null ? 0L : atomic.get();
083  }
084
085  /**
086   * Increments by one the value currently associated with {@code key}, and returns the new value.
087   */
088  @CanIgnoreReturnValue
089  public long incrementAndGet(K key) {
090    return addAndGet(key, 1);
091  }
092
093  /**
094   * Decrements by one the value currently associated with {@code key}, and returns the new value.
095   */
096  @CanIgnoreReturnValue
097  public long decrementAndGet(K key) {
098    return addAndGet(key, -1);
099  }
100
101  /**
102   * Adds {@code delta} to the value currently associated with {@code key}, and returns the new
103   * value.
104   */
105  @CanIgnoreReturnValue
106  public long addAndGet(K key, long delta) {
107    outer:
108    while (true) {
109      AtomicLong atomic = map.get(key);
110      if (atomic == null) {
111        atomic = map.putIfAbsent(key, new AtomicLong(delta));
112        if (atomic == null) {
113          return delta;
114        }
115        // atomic is now non-null; fall through
116      }
117
118      while (true) {
119        long oldValue = atomic.get();
120        if (oldValue == 0L) {
121          // don't compareAndSet a zero
122          if (map.replace(key, atomic, new AtomicLong(delta))) {
123            return delta;
124          }
125          // atomic replaced
126          continue outer;
127        }
128
129        long newValue = oldValue + delta;
130        if (atomic.compareAndSet(oldValue, newValue)) {
131          return newValue;
132        }
133        // value changed
134      }
135    }
136  }
137
138  /**
139   * Increments by one the value currently associated with {@code key}, and returns the old value.
140   */
141  @CanIgnoreReturnValue
142  public long getAndIncrement(K key) {
143    return getAndAdd(key, 1);
144  }
145
146  /**
147   * Decrements by one the value currently associated with {@code key}, and returns the old value.
148   */
149  @CanIgnoreReturnValue
150  public long getAndDecrement(K key) {
151    return getAndAdd(key, -1);
152  }
153
154  /**
155   * Adds {@code delta} to the value currently associated with {@code key}, and returns the old
156   * value.
157   */
158  @CanIgnoreReturnValue
159  public long getAndAdd(K key, long delta) {
160    outer:
161    while (true) {
162      AtomicLong atomic = map.get(key);
163      if (atomic == null) {
164        atomic = map.putIfAbsent(key, new AtomicLong(delta));
165        if (atomic == null) {
166          return 0L;
167        }
168        // atomic is now non-null; fall through
169      }
170
171      while (true) {
172        long oldValue = atomic.get();
173        if (oldValue == 0L) {
174          // don't compareAndSet a zero
175          if (map.replace(key, atomic, new AtomicLong(delta))) {
176            return 0L;
177          }
178          // atomic replaced
179          continue outer;
180        }
181
182        long newValue = oldValue + delta;
183        if (atomic.compareAndSet(oldValue, newValue)) {
184          return oldValue;
185        }
186        // value changed
187      }
188    }
189  }
190
191  /**
192   * Associates {@code newValue} with {@code key} in this map, and returns the value previously
193   * associated with {@code key}, or zero if there was no such value.
194   */
195  @CanIgnoreReturnValue
196  public long put(K key, long newValue) {
197    outer:
198    while (true) {
199      AtomicLong atomic = map.get(key);
200      if (atomic == null) {
201        atomic = map.putIfAbsent(key, new AtomicLong(newValue));
202        if (atomic == null) {
203          return 0L;
204        }
205        // atomic is now non-null; fall through
206      }
207
208      while (true) {
209        long oldValue = atomic.get();
210        if (oldValue == 0L) {
211          // don't compareAndSet a zero
212          if (map.replace(key, atomic, new AtomicLong(newValue))) {
213            return 0L;
214          }
215          // atomic replaced
216          continue outer;
217        }
218
219        if (atomic.compareAndSet(oldValue, newValue)) {
220          return oldValue;
221        }
222        // value changed
223      }
224    }
225  }
226
227  /**
228   * Copies all of the mappings from the specified map to this map. The effect of this call is
229   * equivalent to that of calling {@code put(k, v)} on this map once for each mapping from key
230   * {@code k} to value {@code v} in the specified map. The behavior of this operation is undefined
231   * if the specified map is modified while the operation is in progress.
232   */
233  public void putAll(Map<? extends K, ? extends Long> m) {
234    for (Entry<? extends K, ? extends Long> entry : m.entrySet()) {
235      put(entry.getKey(), entry.getValue());
236    }
237  }
238
239  /**
240   * Removes and returns the value associated with {@code key}. If {@code key} is not in the map,
241   * this method has no effect and returns zero.
242   */
243  @CanIgnoreReturnValue
244  public long remove(K key) {
245    AtomicLong atomic = map.get(key);
246    if (atomic == null) {
247      return 0L;
248    }
249
250    while (true) {
251      long oldValue = atomic.get();
252      if (oldValue == 0L || atomic.compareAndSet(oldValue, 0L)) {
253        // only remove after setting to zero, to avoid concurrent updates
254        map.remove(key, atomic);
255        // succeed even if the remove fails, since the value was already adjusted
256        return oldValue;
257      }
258    }
259  }
260
261  /**
262   * Atomically remove {@code key} from the map iff its associated value is 0.
263   *
264   * @since 20.0
265   */
266  @Beta
267  @CanIgnoreReturnValue
268  public boolean removeIfZero(K key) {
269    return remove(key, 0);
270  }
271
272  /**
273   * Removes all mappings from this map whose values are zero.
274   *
275   * <p>This method is not atomic: the map may be visible in intermediate states, where some of the
276   * zero values have been removed and others have not.
277   */
278  public void removeAllZeros() {
279    Iterator<Entry<K, AtomicLong>> entryIterator = map.entrySet().iterator();
280    while (entryIterator.hasNext()) {
281      Entry<K, AtomicLong> entry = entryIterator.next();
282      AtomicLong atomic = entry.getValue();
283      if (atomic != null && atomic.get() == 0L) {
284        entryIterator.remove();
285      }
286    }
287  }
288
289  /**
290   * Returns the sum of all values in this map.
291   *
292   * <p>This method is not atomic: the sum may or may not include other concurrent operations.
293   */
294  public long sum() {
295    long sum = 0L;
296    for (AtomicLong value : map.values()) {
297      sum = sum + value.get();
298    }
299    return sum;
300  }
301
302  @MonotonicNonNullDecl private transient Map<K, Long> asMap;
303
304  /** Returns a live, read-only view of the map backing this {@code AtomicLongMap}. */
305  public Map<K, Long> asMap() {
306    Map<K, Long> result = asMap;
307    return (result == null) ? asMap = createAsMap() : result;
308  }
309
310  private Map<K, Long> createAsMap() {
311    return Collections.unmodifiableMap(
312        Maps.transformValues(
313            map,
314            new Function<AtomicLong, Long>() {
315              @Override
316              public Long apply(AtomicLong atomic) {
317                return atomic.get();
318              }
319            }));
320  }
321
322  /** Returns true if this map contains a mapping for the specified key. */
323  public boolean containsKey(Object key) {
324    return map.containsKey(key);
325  }
326
327  /**
328   * Returns the number of key-value mappings in this map. If the map contains more than {@code
329   * Integer.MAX_VALUE} elements, returns {@code Integer.MAX_VALUE}.
330   */
331  public int size() {
332    return map.size();
333  }
334
335  /** Returns {@code true} if this map contains no key-value mappings. */
336  public boolean isEmpty() {
337    return map.isEmpty();
338  }
339
340  /**
341   * Removes all of the mappings from this map. The map will be empty after this call returns.
342   *
343   * <p>This method is not atomic: the map may not be empty after returning if there were concurrent
344   * writes.
345   */
346  public void clear() {
347    map.clear();
348  }
349
350  @Override
351  public String toString() {
352    return map.toString();
353  }
354
355  /*
356   * ConcurrentMap operations which we may eventually add.
357   *
358   * The problem with these is that remove(K, long) has to be done in two phases by definition ---
359   * first decrementing to zero, and then removing. putIfAbsent or replace could observe the
360   * intermediate zero-state. Ways we could deal with this are:
361   *
362   * - Don't define any of the ConcurrentMap operations. This is the current state of affairs.
363   *
364   * - Define putIfAbsent and replace as treating zero and absent identically (as currently
365   *   implemented below). This is a bit surprising with putIfAbsent, which really becomes
366   *   putIfZero.
367   *
368   * - Allow putIfAbsent and replace to distinguish between zero and absent, but don't implement
369   *   remove(K, long). Without any two-phase operations it becomes feasible for all remaining
370   *   operations to distinguish between zero and absent. If we do this, then perhaps we should add
371   *   replace(key, long).
372   *
373   * - Introduce a special-value private static final AtomicLong that would have the meaning of
374   *   removal-in-progress, and rework all operations to properly distinguish between zero and
375   *   absent.
376   */
377
378  /**
379   * If {@code key} is not already associated with a value or if {@code key} is associated with
380   * zero, associate it with {@code newValue}. Returns the previous value associated with {@code
381   * key}, or zero if there was no mapping for {@code key}.
382   */
383  long putIfAbsent(K key, long newValue) {
384    while (true) {
385      AtomicLong atomic = map.get(key);
386      if (atomic == null) {
387        atomic = map.putIfAbsent(key, new AtomicLong(newValue));
388        if (atomic == null) {
389          return 0L;
390        }
391        // atomic is now non-null; fall through
392      }
393
394      long oldValue = atomic.get();
395      if (oldValue == 0L) {
396        // don't compareAndSet a zero
397        if (map.replace(key, atomic, new AtomicLong(newValue))) {
398          return 0L;
399        }
400        // atomic replaced
401        continue;
402      }
403
404      return oldValue;
405    }
406  }
407
408  /**
409   * If {@code (key, expectedOldValue)} is currently in the map, this method replaces {@code
410   * expectedOldValue} with {@code newValue} and returns true; otherwise, this method returns false.
411   *
412   * <p>If {@code expectedOldValue} is zero, this method will succeed if {@code (key, zero)} is
413   * currently in the map, or if {@code key} is not in the map at all.
414   */
415  boolean replace(K key, long expectedOldValue, long newValue) {
416    if (expectedOldValue == 0L) {
417      return putIfAbsent(key, newValue) == 0L;
418    } else {
419      AtomicLong atomic = map.get(key);
420      return (atomic == null) ? false : atomic.compareAndSet(expectedOldValue, newValue);
421    }
422  }
423
424  /**
425   * If {@code (key, value)} is currently in the map, this method removes it and returns true;
426   * otherwise, this method returns false.
427   */
428  boolean remove(K key, long value) {
429    AtomicLong atomic = map.get(key);
430    if (atomic == null) {
431      return false;
432    }
433
434    long oldValue = atomic.get();
435    if (oldValue != value) {
436      return false;
437    }
438
439    if (oldValue == 0L || atomic.compareAndSet(oldValue, 0L)) {
440      // only remove after setting to zero, to avoid concurrent updates
441      map.remove(key, atomic);
442      // succeed even if the remove fails, since the value was already adjusted
443      return true;
444    }
445
446    // value changed
447    return false;
448  }
449}