Coverage Summary for Class: AtomicLongMap (com.google.common.util.concurrent)

Class Class, % Method, % Line, %
AtomicLongMap 0% (0/1) 0% (0/35) 0% (0/54)


1 /* 2  * Copyright (C) 2011 The Guava Authors 3  * 4  * Licensed under the Apache License, Version 2.0 (the "License"); 5  * you may not use this file except in compliance with the License. 6  * You may obtain a copy of the License at 7  * 8  * http://www.apache.org/licenses/LICENSE-2.0 9  * 10  * Unless required by applicable law or agreed to in writing, software 11  * distributed under the License is distributed on an "AS IS" BASIS, 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13  * See the License for the specific language governing permissions and 14  * limitations under the License. 15  */ 16  17 package com.google.common.util.concurrent; 18  19 import static com.google.common.base.Preconditions.checkNotNull; 20  21 import com.google.common.annotations.Beta; 22 import com.google.common.annotations.GwtCompatible; 23 import com.google.errorprone.annotations.CanIgnoreReturnValue; 24 import java.io.Serializable; 25 import java.util.Collections; 26 import java.util.Map; 27 import java.util.concurrent.ConcurrentHashMap; 28 import java.util.concurrent.atomic.AtomicBoolean; 29 import java.util.concurrent.atomic.AtomicLong; 30 import java.util.function.LongBinaryOperator; 31 import java.util.function.LongUnaryOperator; 32 import javax.annotation.CheckForNull; 33  34 /** 35  * A map containing {@code long} values that can be atomically updated. While writes to a 36  * traditional {@code Map} rely on {@code put(K, V)}, the typical mechanism for writing to this map 37  * is {@code addAndGet(K, long)}, which adds a {@code long} to the value currently associated with 38  * {@code K}. If a key has not yet been associated with a value, its implicit value is zero. 39  * 40  * <p>Most methods in this class treat absent values and zero values identically, as individually 41  * documented. Exceptions to this are {@link #containsKey}, {@link #size}, {@link #isEmpty}, {@link 42  * #asMap}, and {@link #toString}. 43  * 44  * <p>Instances of this class may be used by multiple threads concurrently. All operations are 45  * atomic unless otherwise noted. 46  * 47  * <p><b>Note:</b> If your values are always positive and less than 2^31, you may wish to use a 48  * {@link com.google.common.collect.Multiset} such as {@link 49  * com.google.common.collect.ConcurrentHashMultiset} instead. 50  * 51  * <p><b>Warning:</b> Unlike {@code Multiset}, entries whose values are zero are not automatically 52  * removed from the map. Instead they must be removed manually with {@link #removeAllZeros}. 53  * 54  * @author Charles Fry 55  * @since 11.0 56  */ 57 @GwtCompatible 58 @ElementTypesAreNonnullByDefault 59 public final class AtomicLongMap<K> implements Serializable { 60  private final ConcurrentHashMap<K, Long> map; 61  62  private AtomicLongMap(ConcurrentHashMap<K, Long> map) { 63  this.map = checkNotNull(map); 64  } 65  66  /** Creates an {@code AtomicLongMap}. */ 67  public static <K> AtomicLongMap<K> create() { 68  return new AtomicLongMap<K>(new ConcurrentHashMap<>()); 69  } 70  71  /** Creates an {@code AtomicLongMap} with the same mappings as the specified {@code Map}. */ 72  public static <K> AtomicLongMap<K> create(Map<? extends K, ? extends Long> m) { 73  AtomicLongMap<K> result = create(); 74  result.putAll(m); 75  return result; 76  } 77  78  /** 79  * Returns the value associated with {@code key}, or zero if there is no value associated with 80  * {@code key}. 81  */ 82  public long get(K key) { 83  return map.getOrDefault(key, 0L); 84  } 85  86  /** 87  * Increments by one the value currently associated with {@code key}, and returns the new value. 88  */ 89  @CanIgnoreReturnValue 90  public long incrementAndGet(K key) { 91  return addAndGet(key, 1); 92  } 93  94  /** 95  * Decrements by one the value currently associated with {@code key}, and returns the new value. 96  */ 97  @CanIgnoreReturnValue 98  public long decrementAndGet(K key) { 99  return addAndGet(key, -1); 100  } 101  102  /** 103  * Adds {@code delta} to the value currently associated with {@code key}, and returns the new 104  * value. 105  */ 106  @CanIgnoreReturnValue 107  public long addAndGet(K key, long delta) { 108  return accumulateAndGet(key, delta, Long::sum); 109  } 110  111  /** 112  * Increments by one the value currently associated with {@code key}, and returns the old value. 113  */ 114  @CanIgnoreReturnValue 115  public long getAndIncrement(K key) { 116  return getAndAdd(key, 1); 117  } 118  119  /** 120  * Decrements by one the value currently associated with {@code key}, and returns the old value. 121  */ 122  @CanIgnoreReturnValue 123  public long getAndDecrement(K key) { 124  return getAndAdd(key, -1); 125  } 126  127  /** 128  * Adds {@code delta} to the value currently associated with {@code key}, and returns the old 129  * value. 130  */ 131  @CanIgnoreReturnValue 132  public long getAndAdd(K key, long delta) { 133  return getAndAccumulate(key, delta, Long::sum); 134  } 135  136  /** 137  * Updates the value currently associated with {@code key} with the specified function, and 138  * returns the new value. If there is not currently a value associated with {@code key}, the 139  * function is applied to {@code 0L}. 140  * 141  * @since 21.0 142  */ 143  @CanIgnoreReturnValue 144  public long updateAndGet(K key, LongUnaryOperator updaterFunction) { 145  checkNotNull(updaterFunction); 146  return map.compute( 147  key, (k, value) -> updaterFunction.applyAsLong((value == null) ? 0L : value.longValue())); 148  } 149  150  /** 151  * Updates the value currently associated with {@code key} with the specified function, and 152  * returns the old value. If there is not currently a value associated with {@code key}, the 153  * function is applied to {@code 0L}. 154  * 155  * @since 21.0 156  */ 157  @CanIgnoreReturnValue 158  public long getAndUpdate(K key, LongUnaryOperator updaterFunction) { 159  checkNotNull(updaterFunction); 160  AtomicLong holder = new AtomicLong(); 161  map.compute( 162  key, 163  (k, value) -> { 164  long oldValue = (value == null) ? 0L : value.longValue(); 165  holder.set(oldValue); 166  return updaterFunction.applyAsLong(oldValue); 167  }); 168  return holder.get(); 169  } 170  171  /** 172  * Updates the value currently associated with {@code key} by combining it with {@code x} via the 173  * specified accumulator function, returning the new value. The previous value associated with 174  * {@code key} (or zero, if there is none) is passed as the first argument to {@code 175  * accumulatorFunction}, and {@code x} is passed as the second argument. 176  * 177  * @since 21.0 178  */ 179  @CanIgnoreReturnValue 180  public long accumulateAndGet(K key, long x, LongBinaryOperator accumulatorFunction) { 181  checkNotNull(accumulatorFunction); 182  return updateAndGet(key, oldValue -> accumulatorFunction.applyAsLong(oldValue, x)); 183  } 184  185  /** 186  * Updates the value currently associated with {@code key} by combining it with {@code x} via the 187  * specified accumulator function, returning the old value. The previous value associated with 188  * {@code key} (or zero, if there is none) is passed as the first argument to {@code 189  * accumulatorFunction}, and {@code x} is passed as the second argument. 190  * 191  * @since 21.0 192  */ 193  @CanIgnoreReturnValue 194  public long getAndAccumulate(K key, long x, LongBinaryOperator accumulatorFunction) { 195  checkNotNull(accumulatorFunction); 196  return getAndUpdate(key, oldValue -> accumulatorFunction.applyAsLong(oldValue, x)); 197  } 198  199  /** 200  * Associates {@code newValue} with {@code key} in this map, and returns the value previously 201  * associated with {@code key}, or zero if there was no such value. 202  */ 203  @CanIgnoreReturnValue 204  public long put(K key, long newValue) { 205  return getAndUpdate(key, x -> newValue); 206  } 207  208  /** 209  * Copies all of the mappings from the specified map to this map. The effect of this call is 210  * equivalent to that of calling {@code put(k, v)} on this map once for each mapping from key 211  * {@code k} to value {@code v} in the specified map. The behavior of this operation is undefined 212  * if the specified map is modified while the operation is in progress. 213  */ 214  public void putAll(Map<? extends K, ? extends Long> m) { 215  m.forEach(this::put); 216  } 217  218  /** 219  * Removes and returns the value associated with {@code key}. If {@code key} is not in the map, 220  * this method has no effect and returns zero. 221  */ 222  @CanIgnoreReturnValue 223  public long remove(K key) { 224  Long result = map.remove(key); 225  return (result == null) ? 0L : result.longValue(); 226  } 227  228  /** 229  * If {@code (key, value)} is currently in the map, this method removes it and returns true; 230  * otherwise, this method returns false. 231  */ 232  boolean remove(K key, long value) { 233  return map.remove(key, value); 234  } 235  236  /** 237  * Atomically remove {@code key} from the map iff its associated value is 0. 238  * 239  * @since 20.0 240  */ 241  @Beta 242  @CanIgnoreReturnValue 243  public boolean removeIfZero(K key) { 244  return remove(key, 0); 245  } 246  247  /** 248  * Removes all mappings from this map whose values are zero. 249  * 250  * <p>This method is not atomic: the map may be visible in intermediate states, where some of the 251  * zero values have been removed and others have not. 252  */ 253  public void removeAllZeros() { 254  map.values().removeIf(x -> x == 0); 255  } 256  257  /** 258  * Returns the sum of all values in this map. 259  * 260  * <p>This method is not atomic: the sum may or may not include other concurrent operations. 261  */ 262  public long sum() { 263  return map.values().stream().mapToLong(Long::longValue).sum(); 264  } 265  266  @CheckForNull private transient Map<K, Long> asMap; 267  268  /** Returns a live, read-only view of the map backing this {@code AtomicLongMap}. */ 269  public Map<K, Long> asMap() { 270  Map<K, Long> result = asMap; 271  return (result == null) ? asMap = createAsMap() : result; 272  } 273  274  private Map<K, Long> createAsMap() { 275  return Collections.unmodifiableMap(map); 276  } 277  278  /** Returns true if this map contains a mapping for the specified key. */ 279  public boolean containsKey(Object key) { 280  return map.containsKey(key); 281  } 282  283  /** 284  * Returns the number of key-value mappings in this map. If the map contains more than {@code 285  * Integer.MAX_VALUE} elements, returns {@code Integer.MAX_VALUE}. 286  */ 287  public int size() { 288  return map.size(); 289  } 290  291  /** Returns {@code true} if this map contains no key-value mappings. */ 292  public boolean isEmpty() { 293  return map.isEmpty(); 294  } 295  296  /** 297  * Removes all of the mappings from this map. The map will be empty after this call returns. 298  * 299  * <p>This method is not atomic: the map may not be empty after returning if there were concurrent 300  * writes. 301  */ 302  public void clear() { 303  map.clear(); 304  } 305  306  @Override 307  public String toString() { 308  return map.toString(); 309  } 310  311  /** 312  * If {@code key} is not already associated with a value or if {@code key} is associated with 313  * zero, associate it with {@code newValue}. Returns the previous value associated with {@code 314  * key}, or zero if there was no mapping for {@code key}. 315  */ 316  long putIfAbsent(K key, long newValue) { 317  AtomicBoolean noValue = new AtomicBoolean(false); 318  Long result = 319  map.compute( 320  key, 321  (k, oldValue) -> { 322  if (oldValue == null || oldValue == 0) { 323  noValue.set(true); 324  return newValue; 325  } else { 326  return oldValue; 327  } 328  }); 329  return noValue.get() ? 0L : result.longValue(); 330  } 331  332  /** 333  * If {@code (key, expectedOldValue)} is currently in the map, this method replaces {@code 334  * expectedOldValue} with {@code newValue} and returns true; otherwise, this method returns false. 335  * 336  * <p>If {@code expectedOldValue} is zero, this method will succeed if {@code (key, zero)} is 337  * currently in the map, or if {@code key} is not in the map at all. 338  */ 339  boolean replace(K key, long expectedOldValue, long newValue) { 340  if (expectedOldValue == 0L) { 341  return putIfAbsent(key, newValue) == 0L; 342  } else { 343  return map.replace(key, expectedOldValue, newValue); 344  } 345  } 346 }