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;
031
032/**
033 * A map containing {@code long} values that can be atomically updated. While writes to a
034 * traditional {@code Map} rely on {@code put(K, V)}, the typical mechanism for writing to this map
035 * is {@code addAndGet(K, long)}, which adds a {@code long} to the value currently associated with
036 * {@code K}. If a key has not yet been associated with a value, its implicit value is zero.
037 *
038 * <p>Most methods in this class treat absent values and zero values identically, as individually
039 * documented. Exceptions to this are {@link #containsKey}, {@link #size}, {@link #isEmpty}, {@link
040 * #asMap}, and {@link #toString}.
041 *
042 * <p>Instances of this class may be used by multiple threads concurrently. All operations are
043 * atomic unless otherwise noted.
044 *
045 * <p><b>Note:</b> If your values are always positive and less than 2^31, you may wish to use a
046 * {@link com.google.common.collect.Multiset} such as {@link
047 * com.google.common.collect.ConcurrentHashMultiset} instead.
048 *
049 * <b>Warning:</b> Unlike {@code Multiset}, entries whose values are zero are not automatically
050 * removed from the map. Instead they must be removed manually with {@link #removeAllZeros}.
051 *
052 * @author Charles Fry
053 * @since 11.0
054 */
055@GwtCompatible
056public final class AtomicLongMap<K> implements Serializable {
057  private final ConcurrentHashMap<K, AtomicLong> map;
058
059  private AtomicLongMap(ConcurrentHashMap<K, AtomicLong> map) {
060    this.map = checkNotNull(map);
061  }
062
063  /**
064   * Creates an {@code AtomicLongMap}.
065   */
066  public static <K> AtomicLongMap<K> create() {
067    return new AtomicLongMap<K>(new ConcurrentHashMap<K, AtomicLong>());
068  }
069
070  /**
071   * Creates an {@code AtomicLongMap} with the same mappings as the specified {@code Map}.
072   */
073  public static <K> AtomicLongMap<K> create(Map<? extends K, ? extends Long> m) {
074    AtomicLongMap<K> result = create();
075    result.putAll(m);
076    return result;
077  }
078
079  /**
080   * Returns the value associated with {@code key}, or zero if there is no value associated with
081   * {@code key}.
082   */
083  public long get(K key) {
084    AtomicLong atomic = map.get(key);
085    return atomic == null ? 0L : atomic.get();
086  }
087
088  /**
089   * Increments by one the value currently associated with {@code key}, and returns the new value.
090   */
091  @CanIgnoreReturnValue
092  public long incrementAndGet(K key) {
093    return addAndGet(key, 1);
094  }
095
096  /**
097   * Decrements by one the value currently associated with {@code key}, and returns the new value.
098   */
099  @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    outer:
111    while (true) {
112      AtomicLong atomic = map.get(key);
113      if (atomic == null) {
114        atomic = map.putIfAbsent(key, new AtomicLong(delta));
115        if (atomic == null) {
116          return delta;
117        }
118        // atomic is now non-null; fall through
119      }
120
121      while (true) {
122        long oldValue = atomic.get();
123        if (oldValue == 0L) {
124          // don't compareAndSet a zero
125          if (map.replace(key, atomic, new AtomicLong(delta))) {
126            return delta;
127          }
128          // atomic replaced
129          continue outer;
130        }
131
132        long newValue = oldValue + delta;
133        if (atomic.compareAndSet(oldValue, newValue)) {
134          return newValue;
135        }
136        // value changed
137      }
138    }
139  }
140
141  /**
142   * Increments by one the value currently associated with {@code key}, and returns the old value.
143   */
144  @CanIgnoreReturnValue
145  public long getAndIncrement(K key) {
146    return getAndAdd(key, 1);
147  }
148
149  /**
150   * Decrements by one the value currently associated with {@code key}, and returns the old value.
151   */
152  @CanIgnoreReturnValue
153  public long getAndDecrement(K key) {
154    return getAndAdd(key, -1);
155  }
156
157  /**
158   * Adds {@code delta} to the value currently associated with {@code key}, and returns the old
159   * value.
160   */
161  @CanIgnoreReturnValue
162  public long getAndAdd(K key, long delta) {
163    outer:
164    while (true) {
165      AtomicLong atomic = map.get(key);
166      if (atomic == null) {
167        atomic = map.putIfAbsent(key, new AtomicLong(delta));
168        if (atomic == null) {
169          return 0L;
170        }
171        // atomic is now non-null; fall through
172      }
173
174      while (true) {
175        long oldValue = atomic.get();
176        if (oldValue == 0L) {
177          // don't compareAndSet a zero
178          if (map.replace(key, atomic, new AtomicLong(delta))) {
179            return 0L;
180          }
181          // atomic replaced
182          continue outer;
183        }
184
185        long newValue = oldValue + delta;
186        if (atomic.compareAndSet(oldValue, newValue)) {
187          return oldValue;
188        }
189        // value changed
190      }
191    }
192  }
193
194  /**
195   * Associates {@code newValue} with {@code key} in this map, and returns the value previously
196   * associated with {@code key}, or zero if there was no such value.
197   */
198  @CanIgnoreReturnValue
199  public long put(K key, long newValue) {
200    outer:
201    while (true) {
202      AtomicLong atomic = map.get(key);
203      if (atomic == null) {
204        atomic = map.putIfAbsent(key, new AtomicLong(newValue));
205        if (atomic == null) {
206          return 0L;
207        }
208        // atomic is now non-null; fall through
209      }
210
211      while (true) {
212        long oldValue = atomic.get();
213        if (oldValue == 0L) {
214          // don't compareAndSet a zero
215          if (map.replace(key, atomic, new AtomicLong(newValue))) {
216            return 0L;
217          }
218          // atomic replaced
219          continue outer;
220        }
221
222        if (atomic.compareAndSet(oldValue, newValue)) {
223          return oldValue;
224        }
225        // value changed
226      }
227    }
228  }
229
230  /**
231   * Copies all of the mappings from the specified map to this map. The effect of this call is
232   * equivalent to that of calling {@code put(k, v)} on this map once for each mapping from key
233   * {@code k} to value {@code v} in the specified map. The behavior of this operation is undefined
234   * if the specified map is modified while the operation is in progress.
235   */
236  public void putAll(Map<? extends K, ? extends Long> m) {
237    for (Entry<? extends K, ? extends Long> entry : m.entrySet()) {
238      put(entry.getKey(), entry.getValue());
239    }
240  }
241
242  /**
243   * Removes and returns the value associated with {@code key}. If {@code key} is not in the map,
244   * this method has no effect and returns zero.
245   */
246  @CanIgnoreReturnValue
247  public long remove(K key) {
248    AtomicLong atomic = map.get(key);
249    if (atomic == null) {
250      return 0L;
251    }
252
253    while (true) {
254      long oldValue = atomic.get();
255      if (oldValue == 0L || atomic.compareAndSet(oldValue, 0L)) {
256        // only remove after setting to zero, to avoid concurrent updates
257        map.remove(key, atomic);
258        // succeed even if the remove fails, since the value was already adjusted
259        return oldValue;
260      }
261    }
262  }
263
264  /**
265   * Atomically remove {@code key} from the map iff its associated value is 0.
266   *
267   * @since 20.0
268   */
269  @Beta
270  @CanIgnoreReturnValue
271  public boolean removeIfZero(K key) {
272    return remove(key, 0);
273  }
274
275  /**
276   * Removes all mappings from this map whose values are zero.
277   *
278   * <p>This method is not atomic: the map may be visible in intermediate states, where some of the
279   * zero values have been removed and others have not.
280   */
281  public void removeAllZeros() {
282    Iterator<Entry<K, AtomicLong>> entryIterator = map.entrySet().iterator();
283    while (entryIterator.hasNext()) {
284      Entry<K, AtomicLong> entry = entryIterator.next();
285      AtomicLong atomic = entry.getValue();
286      if (atomic != null && atomic.get() == 0L) {
287        entryIterator.remove();
288      }
289    }
290  }
291
292  /**
293   * Returns the sum of all values in this map.
294   *
295   * <p>This method is not atomic: the sum may or may not include other concurrent operations.
296   */
297  public long sum() {
298    long sum = 0L;
299    for (AtomicLong value : map.values()) {
300      sum = sum + value.get();
301    }
302    return sum;
303  }
304
305  private transient Map<K, Long> asMap;
306
307  /**
308   * Returns a live, read-only view of the map backing this {@code AtomicLongMap}.
309   */
310  public Map<K, Long> asMap() {
311    Map<K, Long> result = asMap;
312    return (result == null) ? asMap = createAsMap() : result;
313  }
314
315  private Map<K, Long> createAsMap() {
316    return Collections.unmodifiableMap(
317        Maps.transformValues(
318            map,
319            new Function<AtomicLong, Long>() {
320              @Override
321              public Long apply(AtomicLong atomic) {
322                return atomic.get();
323              }
324            }));
325  }
326
327  /**
328   * Returns true if this map contains a mapping for the specified key.
329   */
330  public boolean containsKey(Object key) {
331    return map.containsKey(key);
332  }
333
334  /**
335   * Returns the number of key-value mappings in this map. If the map contains more than {@code
336   * Integer.MAX_VALUE} elements, returns {@code Integer.MAX_VALUE}.
337   */
338  public int size() {
339    return map.size();
340  }
341
342  /**
343   * Returns {@code true} if this map contains no key-value mappings.
344   */
345  public boolean isEmpty() {
346    return map.isEmpty();
347  }
348
349  /**
350   * Removes all of the mappings from this map. The map will be empty after this call returns.
351   *
352   * <p>This method is not atomic: the map may not be empty after returning if there were concurrent
353   * writes.
354   */
355  public void clear() {
356    map.clear();
357  }
358
359  @Override
360  public String toString() {
361    return map.toString();
362  }
363
364  /*
365   * ConcurrentMap operations which we may eventually add.
366   *
367   * The problem with these is that remove(K, long) has to be done in two phases by definition ---
368   * first decrementing to zero, and then removing. putIfAbsent or replace could observe the
369   * intermediate zero-state. Ways we could deal with this are:
370   *
371   * - Don't define any of the ConcurrentMap operations. This is the current state of affairs.
372   *
373   * - Define putIfAbsent and replace as treating zero and absent identically (as currently
374   *   implemented below). This is a bit surprising with putIfAbsent, which really becomes
375   *   putIfZero.
376   *
377   * - Allow putIfAbsent and replace to distinguish between zero and absent, but don't implement
378   *   remove(K, long). Without any two-phase operations it becomes feasible for all remaining
379   *   operations to distinguish between zero and absent. If we do this, then perhaps we should add
380   *   replace(key, long).
381   *
382   * - Introduce a special-value private static final AtomicLong that would have the meaning of
383   *   removal-in-progress, and rework all operations to properly distinguish between zero and
384   *   absent.
385   */
386
387  /**
388   * If {@code key} is not already associated with a value or if {@code key} is associated with
389   * zero, associate it with {@code newValue}. Returns the previous value associated with {@code
390   * key}, or zero if there was no mapping for {@code key}.
391   */
392  long putIfAbsent(K key, long newValue) {
393    while (true) {
394      AtomicLong atomic = map.get(key);
395      if (atomic == null) {
396        atomic = map.putIfAbsent(key, new AtomicLong(newValue));
397        if (atomic == null) {
398          return 0L;
399        }
400        // atomic is now non-null; fall through
401      }
402
403      long oldValue = atomic.get();
404      if (oldValue == 0L) {
405        // don't compareAndSet a zero
406        if (map.replace(key, atomic, new AtomicLong(newValue))) {
407          return 0L;
408        }
409        // atomic replaced
410        continue;
411      }
412
413      return oldValue;
414    }
415  }
416
417  /**
418   * If {@code (key, expectedOldValue)} is currently in the map, this method replaces {@code
419   * expectedOldValue} with {@code newValue} and returns true; otherwise, this method returns false.
420   *
421   * <p>If {@code expectedOldValue} is zero, this method will succeed if {@code (key, zero)} is
422   * currently in the map, or if {@code key} is not in the map at all.
423   */
424  boolean replace(K key, long expectedOldValue, long newValue) {
425    if (expectedOldValue == 0L) {
426      return putIfAbsent(key, newValue) == 0L;
427    } else {
428      AtomicLong atomic = map.get(key);
429      return (atomic == null) ? false : atomic.compareAndSet(expectedOldValue, newValue);
430    }
431  }
432
433  /**
434   * If {@code (key, value)} is currently in the map, this method removes it and returns true;
435   * otherwise, this method returns false.
436   */
437  boolean remove(K key, long value) {
438    AtomicLong atomic = map.get(key);
439    if (atomic == null) {
440      return false;
441    }
442
443    long oldValue = atomic.get();
444    if (oldValue != value) {
445      return false;
446    }
447
448    if (oldValue == 0L || atomic.compareAndSet(oldValue, 0L)) {
449      // only remove after setting to zero, to avoid concurrent updates
450      map.remove(key, atomic);
451      // succeed even if the remove fails, since the value was already adjusted
452      return true;
453    }
454
455    // value changed
456    return false;
457  }
458}