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