AtomicLongMap.java revision 1d580d0f6ee4f21eb309ba7b509d2c6d671c4044
1// Copyright 2011 Google Inc. All Rights Reserved.
2
3package com.google.common.util.concurrent;
4
5import static com.google.common.base.Preconditions.checkNotNull;
6
7import com.google.common.annotations.Beta;
8import com.google.common.base.Function;
9import com.google.common.collect.Maps;
10
11import java.util.Collections;
12import java.util.Map;
13import java.util.concurrent.ConcurrentHashMap;
14import java.util.concurrent.atomic.AtomicLong;
15
16/**
17 * A map containing {@code long} values that can be atomically updated. While writes to a
18 * traditional {@code Map} rely on {@code put(K, V)}, the typical mechanism for writing to this map
19 * is {@code addAndGet(K, long)}, which adds a {@code long} to the value currently associated with
20 * {@code K}. If a key has not yet been associated with a value, its implicit value is zero.
21 *
22 * <p>Most methods in this class treat absent values and zero values identically, as individually
23 * documented. Exceptions to this are {@link #containsKey}, {@link #size}, {@link #isEmpty},
24 * {@link #asMap}, and {@link #toString}.
25 *
26 * <p>Instances of this class may be used by multiple threads concurrently. All operations are
27 * atomic unless otherwise noted.
28 *
29 * <p><b>Note:</b> If your values are always positive and less than 2^31, you may wish to use a
30 * {@link com.google.common.collect.Multiset} such as
31 * {@link com.google.common.collect.ConcurrentHashMultiset} instead.
32 *
33 * <b>Warning:</b> Unlike {@code Multiset}, entries whose values are zero are not automatically
34 * removed from the map. Instead they must be removed manually with {@link #removeAllZeros}.
35 *
36 * @author Charles Fry
37 * @since 11.0
38 */
39@Beta
40public final class AtomicLongMap<K> {
41  private final ConcurrentHashMap<K, AtomicLong> map;
42
43  private AtomicLongMap(ConcurrentHashMap<K, AtomicLong> map) {
44    this.map = checkNotNull(map);
45  }
46
47  /**
48   * Creates an {@code AtomicLongMap}.
49   */
50  public static <K> AtomicLongMap<K> create() {
51    return new AtomicLongMap<K>(new ConcurrentHashMap<K, AtomicLong>());
52  }
53
54  /**
55   * Creates an {@code AtomicLongMap} with the same mappings as the specified {@code Map}.
56   */
57  public static <K> AtomicLongMap<K> create(Map<? extends K, ? extends Long> m) {
58    AtomicLongMap<K> result = create();
59    result.putAll(m);
60    return result;
61  }
62
63  /**
64   * Returns the value associated with {@code key}, or zero if there is no value associated with
65   * {@code key}.
66   */
67  public long get(K key) {
68    AtomicLong atomic = map.get(key);
69    return atomic == null ? 0L : atomic.get();
70  }
71
72  /**
73   * Increments by one the value currently associated with {@code key}, and returns the new value.
74   */
75  public long incrementAndGet(K key) {
76    return addAndGet(key, 1);
77  }
78
79  /**
80   * Decrements by one the value currently associated with {@code key}, and returns the new value.
81   */
82  public long decrementAndGet(K key) {
83    return addAndGet(key, -1);
84  }
85
86  /**
87   * Adds {@code delta} to the value currently associated with {@code key}, and returns the new
88   * value.
89   */
90  public long addAndGet(K key, long delta) {
91    outer: for (;;) {
92      AtomicLong atomic = map.get(key);
93      if (atomic == null) {
94        atomic = map.putIfAbsent(key, new AtomicLong(delta));
95        if (atomic == null) {
96          return delta;
97        }
98        // atomic is now non-null; fall through
99      }
100
101      for (;;) {
102        long oldValue = atomic.get();
103        if (oldValue == 0L) {
104          // don't compareAndSet a zero
105          if (map.replace(key, atomic, new AtomicLong(delta))) {
106            return delta;
107          }
108          // atomic replaced
109          continue outer;
110        }
111
112        long newValue = oldValue + delta;
113        if (atomic.compareAndSet(oldValue, newValue)) {
114          return newValue;
115        }
116        // value changed
117      }
118    }
119  }
120
121  /**
122   * Increments by one the value currently associated with {@code key}, and returns the old value.
123   */
124  public long getAndIncrement(K key) {
125    return getAndAdd(key, 1);
126  }
127
128  /**
129   * Decrements by one the value currently associated with {@code key}, and returns the old value.
130   */
131  public long getAndDecrement(K key) {
132    return getAndAdd(key, -1);
133  }
134
135  /**
136   * Adds {@code delta} to the value currently associated with {@code key}, and returns the old
137   * value.
138   */
139  public long getAndAdd(K key, long delta) {
140    outer: for (;;) {
141      AtomicLong atomic = map.get(key);
142      if (atomic == null) {
143        atomic = map.putIfAbsent(key, new AtomicLong(delta));
144        if (atomic == null) {
145          return 0L;
146        }
147        // atomic is now non-null; fall through
148      }
149
150      for (;;) {
151        long oldValue = atomic.get();
152        if (oldValue == 0L) {
153          // don't compareAndSet a zero
154          if (map.replace(key, atomic, new AtomicLong(delta))) {
155            return 0L;
156          }
157          // atomic replaced
158          continue outer;
159        }
160
161        long newValue = oldValue + delta;
162        if (atomic.compareAndSet(oldValue, newValue)) {
163          return oldValue;
164        }
165        // value changed
166      }
167    }
168  }
169
170  /**
171   * Associates {@code newValue} with {@code key} in this map, and returns the value previously
172   * associated with {@code key}, or zero if there was no such value.
173   */
174  public long put(K key, long newValue) {
175    outer: for (;;) {
176      AtomicLong atomic = map.get(key);
177      if (atomic == null) {
178        atomic = map.putIfAbsent(key, new AtomicLong(newValue));
179        if (atomic == null) {
180          return 0L;
181        }
182        // atomic is now non-null; fall through
183      }
184
185      for (;;) {
186        long oldValue = atomic.get();
187        if (oldValue == 0L) {
188          // don't compareAndSet a zero
189          if (map.replace(key, atomic, new AtomicLong(newValue))) {
190            return 0L;
191          }
192          // atomic replaced
193          continue outer;
194        }
195
196        if (atomic.compareAndSet(oldValue, newValue)) {
197          return oldValue;
198        }
199        // value changed
200      }
201    }
202  }
203
204  /**
205   * Copies all of the mappings from the specified map to this map. The effect of this call is
206   * equivalent to that of calling {@code put(k, v)} on this map once for each mapping from key
207   * {@code k} to value {@code v} in the specified map. The behavior of this operation is undefined
208   * if the specified map is modified while the operation is in progress.
209   */
210  public void putAll(Map<? extends K, ? extends Long> m) {
211    for (Map.Entry<? extends K, ? extends Long> entry : m.entrySet()) {
212      put(entry.getKey(), entry.getValue());
213    }
214  }
215
216  /**
217   * Removes and returns the value associated with {@code key}. If {@code key} is not
218   * in the map, this method has no effect and returns zero.
219   */
220  public long remove(K key) {
221    AtomicLong atomic = map.get(key);
222    if (atomic == null) {
223      return 0L;
224    }
225
226    for (;;) {
227      long oldValue = atomic.get();
228      if (oldValue == 0L || atomic.compareAndSet(oldValue, 0L)) {
229        // only remove after setting to zero, to avoid concurrent updates
230        map.remove(key, atomic);
231        // succeed even if the remove fails, since the value was already adjusted
232        return oldValue;
233      }
234    }
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
241   * of the zero values have been removed and others have not.
242   */
243  public void removeAllZeros() {
244    for (K key : map.keySet()) {
245      AtomicLong atomic = map.get(key);
246      if (atomic != null && atomic.get() == 0L) {
247        map.remove(key, atomic);
248      }
249    }
250  }
251
252  /**
253   * Returns the sum of all values in this map.
254   *
255   * <p>This method is not atomic: the sum may or may not include other concurrent operations.
256   */
257  public long sum() {
258    long sum = 0L;
259    for (AtomicLong value : map.values()) {
260      sum = sum + value.get();
261    }
262    return sum;
263  }
264
265  private transient Map<K, Long> asMap;
266
267  /**
268   * Returns a live, read-only view of the map backing this {@code AtomicLongMap}.
269   */
270  public Map<K, Long> asMap() {
271    Map<K, Long> result = asMap;
272    return (result == null) ? asMap = createAsMap() : result;
273  }
274
275  private Map<K, Long> createAsMap() {
276    return Collections.unmodifiableMap(
277        Maps.transformValues(map, new Function<AtomicLong, Long>() {
278          @Override
279          public Long apply(AtomicLong atomic) {
280            return atomic.get();
281          }
282        }));
283  }
284
285  /**
286   * Returns true if this map contains a mapping for the specified key.
287   */
288  public boolean containsKey(Object key) {
289    return map.containsKey(key);
290  }
291
292  /**
293   * Returns the number of key-value mappings in this map. If the map contains more than
294   * {@code Integer.MAX_VALUE} elements, returns {@code Integer.MAX_VALUE}.
295   */
296  public int size() {
297    return map.size();
298  }
299
300  /**
301   * Returns {@code true} if this map contains no key-value mappings.
302   */
303  public boolean isEmpty() {
304    return map.isEmpty();
305  }
306
307  /**
308   * Removes all of the mappings from this map. The map will be empty after this call returns.
309   *
310   * <p>This method is not atomic: the map may not be empty after returning if there were concurrent
311   * writes.
312   */
313  public void clear() {
314    map.clear();
315  }
316
317  @Override
318  public String toString() {
319    return map.toString();
320  }
321
322  /*
323   * ConcurrentMap operations which we may eventually add.
324   *
325   * The problem with these is that remove(K, long) has to be done in two phases by definition ---
326   * first decrementing to zero, and then removing. putIfAbsent or replace could observe the
327   * intermediate zero-state. Ways we could deal with this are:
328   *
329   * - Don't define any of the ConcurrentMap operations. This is the current state of affairs.
330   *
331   * - Define putIfAbsent and replace as treating zero and absent identically (as currently
332   *   implemented below). This is a bit surprising with putIfAbsent, which really becomes
333   *   putIfZero.
334   *
335   * - Allow putIfAbsent and replace to distinguish between zero and absent, but don't implement
336   *   remove(K, long). Without any two-phase operations it becomes feasible for all remaining
337   *   operations to distinguish between zero and absent. If we do this, then perhaps we should add
338   *   replace(key, long).
339   *
340   * - Introduce a special-value private static final AtomicLong that would have the meaning of
341   *   removal-in-progress, and rework all operations to properly distinguish between zero and
342   *   absent.
343   */
344
345  /**
346   * If {@code key} is not already associated with a value or if {@code key} is associated with
347   * zero, associate it with {@code newValue}. Returns the previous value associated with
348   * {@code key}, or zero if there was no mapping for {@code key}.
349   */
350  long putIfAbsent(K key, long newValue) {
351    for (;;) {
352      AtomicLong atomic = map.get(key);
353      if (atomic == null) {
354        atomic = map.putIfAbsent(key, new AtomicLong(newValue));
355        if (atomic == null) {
356          return 0L;
357        }
358        // atomic is now non-null; fall through
359      }
360
361      long oldValue = atomic.get();
362      if (oldValue == 0L) {
363        // don't compareAndSet a zero
364        if (map.replace(key, atomic, new AtomicLong(newValue))) {
365          return 0L;
366        }
367        // atomic replaced
368        continue;
369      }
370
371      return oldValue;
372    }
373  }
374
375  /**
376   * If {@code (key, expectedOldValue)} is currently in the map, this method replaces
377   * {@code expectedOldValue} with {@code newValue} and returns true; otherwise, this method
378   * returns false.
379   *
380   * <p>If {@code expectedOldValue} is zero, this method will succeed if {@code (key, zero)}
381   * is currently in the map, or if {@code key} is not in the map at all.
382   */
383  boolean replace(K key, long expectedOldValue, long newValue) {
384    if (expectedOldValue == 0L) {
385      return putIfAbsent(key, newValue) == 0L;
386    } else {
387      AtomicLong atomic = map.get(key);
388      return (atomic == null) ? false : atomic.compareAndSet(expectedOldValue, newValue);
389    }
390  }
391
392  /**
393   * If {@code (key, value)} is currently in the map, this method removes it and returns
394   * true; otherwise, this method returns false.
395   */
396  boolean remove(K key, long value) {
397    AtomicLong atomic = map.get(key);
398    if (atomic == null) {
399      return false;
400    }
401
402    long oldValue = atomic.get();
403    if (oldValue != value) {
404      return false;
405    }
406
407    if (oldValue == 0L || atomic.compareAndSet(oldValue, 0L)) {
408      // only remove after setting to zero, to avoid concurrent updates
409      map.remove(key, atomic);
410      // succeed even if the remove fails, since the value was already adjusted
411      return true;
412    }
413
414    // value changed
415    return false;
416  }
417
418}
419