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