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