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