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; 020import static java.util.Objects.requireNonNull; 021 022import com.google.common.annotations.GwtCompatible; 023import com.google.common.annotations.J2ktIncompatible; 024import com.google.errorprone.annotations.CanIgnoreReturnValue; 025import com.google.errorprone.annotations.concurrent.LazyInit; 026import java.io.Serializable; 027import java.util.Collections; 028import java.util.Map; 029import java.util.concurrent.ConcurrentHashMap; 030import java.util.concurrent.atomic.AtomicBoolean; 031import java.util.concurrent.atomic.AtomicLong; 032import java.util.function.LongBinaryOperator; 033import java.util.function.LongUnaryOperator; 034import javax.annotation.CheckForNull; 035 036/** 037 * A map containing {@code long} values that can be atomically updated. While writes to a 038 * traditional {@code Map} rely on {@code put(K, V)}, the typical mechanism for writing to this map 039 * is {@code addAndGet(K, long)}, which adds a {@code long} to the value currently associated with 040 * {@code K}. If a key has not yet been associated with a value, its implicit value is zero. 041 * 042 * <p>Most methods in this class treat absent values and zero values identically, as individually 043 * documented. Exceptions to this are {@link #containsKey}, {@link #size}, {@link #isEmpty}, {@link 044 * #asMap}, and {@link #toString}. 045 * 046 * <p>Instances of this class may be used by multiple threads concurrently. All operations are 047 * atomic unless otherwise noted. 048 * 049 * <p>Instances of this class are serializable if the keys are serializable. 050 * 051 * <p><b>Note:</b> If your values are always positive and less than 2^31, you may wish to use a 052 * {@link com.google.common.collect.Multiset} such as {@link 053 * com.google.common.collect.ConcurrentHashMultiset} instead. 054 * 055 * <p><b>Warning:</b> Unlike {@code Multiset}, entries whose values are zero are not automatically 056 * removed from the map. Instead they must be removed manually with {@link #removeAllZeros}. 057 * 058 * @author Charles Fry 059 * @since 11.0 060 */ 061@GwtCompatible 062@J2ktIncompatible 063@ElementTypesAreNonnullByDefault 064public final class AtomicLongMap<K> implements Serializable { 065 private final ConcurrentHashMap<K, Long> map; 066 067 private AtomicLongMap(ConcurrentHashMap<K, Long> map) { 068 this.map = checkNotNull(map); 069 } 070 071 /** Creates an {@code AtomicLongMap}. */ 072 public static <K> AtomicLongMap<K> create() { 073 return new AtomicLongMap<>(new ConcurrentHashMap<>()); 074 } 075 076 /** Creates an {@code AtomicLongMap} with the same mappings as the specified {@code Map}. */ 077 public static <K> AtomicLongMap<K> create(Map<? extends K, ? extends Long> m) { 078 AtomicLongMap<K> result = create(); 079 result.putAll(m); 080 return result; 081 } 082 083 /** 084 * Returns the value associated with {@code key}, or zero if there is no value associated with 085 * {@code key}. 086 */ 087 public long get(K key) { 088 return map.getOrDefault(key, 0L); 089 } 090 091 /** 092 * Increments by one the value currently associated with {@code key}, and returns the new value. 093 */ 094 @CanIgnoreReturnValue 095 public long incrementAndGet(K key) { 096 return addAndGet(key, 1); 097 } 098 099 /** 100 * Decrements by one the value currently associated with {@code key}, and returns the new value. 101 */ 102 @CanIgnoreReturnValue 103 public long decrementAndGet(K key) { 104 return addAndGet(key, -1); 105 } 106 107 /** 108 * Adds {@code delta} to the value currently associated with {@code key}, and returns the new 109 * value. 110 */ 111 @CanIgnoreReturnValue 112 public long addAndGet(K key, long delta) { 113 return accumulateAndGet(key, delta, Long::sum); 114 } 115 116 /** 117 * Increments by one the value currently associated with {@code key}, and returns the old value. 118 */ 119 @CanIgnoreReturnValue 120 public long getAndIncrement(K key) { 121 return getAndAdd(key, 1); 122 } 123 124 /** 125 * Decrements by one the value currently associated with {@code key}, and returns the old value. 126 */ 127 @CanIgnoreReturnValue 128 public long getAndDecrement(K key) { 129 return getAndAdd(key, -1); 130 } 131 132 /** 133 * Adds {@code delta} to the value currently associated with {@code key}, and returns the old 134 * value. 135 */ 136 @CanIgnoreReturnValue 137 public long getAndAdd(K key, long delta) { 138 return getAndAccumulate(key, delta, Long::sum); 139 } 140 141 /** 142 * Updates the value currently associated with {@code key} with the specified function, and 143 * returns the new value. If there is not currently a value associated with {@code key}, the 144 * function is applied to {@code 0L}. 145 * 146 * @since 21.0 147 */ 148 @CanIgnoreReturnValue 149 public long updateAndGet(K key, LongUnaryOperator updaterFunction) { 150 checkNotNull(updaterFunction); 151 Long result = 152 map.compute( 153 key, 154 (k, value) -> updaterFunction.applyAsLong((value == null) ? 0L : value.longValue())); 155 return requireNonNull(result); 156 } 157 158 /** 159 * Updates the value currently associated with {@code key} with the specified function, and 160 * returns the old value. If there is not currently a value associated with {@code key}, the 161 * function is applied to {@code 0L}. 162 * 163 * @since 21.0 164 */ 165 @CanIgnoreReturnValue 166 public long getAndUpdate(K key, LongUnaryOperator updaterFunction) { 167 checkNotNull(updaterFunction); 168 AtomicLong holder = new AtomicLong(); 169 map.compute( 170 key, 171 (k, value) -> { 172 long oldValue = (value == null) ? 0L : value.longValue(); 173 holder.set(oldValue); 174 return updaterFunction.applyAsLong(oldValue); 175 }); 176 return holder.get(); 177 } 178 179 /** 180 * Updates the value currently associated with {@code key} by combining it with {@code x} via the 181 * specified accumulator function, returning the new value. The previous value associated with 182 * {@code key} (or zero, if there is none) is passed as the first argument to {@code 183 * accumulatorFunction}, and {@code x} is passed as the second argument. 184 * 185 * @since 21.0 186 */ 187 @CanIgnoreReturnValue 188 public long accumulateAndGet(K key, long x, LongBinaryOperator accumulatorFunction) { 189 checkNotNull(accumulatorFunction); 190 return updateAndGet(key, oldValue -> accumulatorFunction.applyAsLong(oldValue, x)); 191 } 192 193 /** 194 * Updates the value currently associated with {@code key} by combining it with {@code x} via the 195 * specified accumulator function, returning the old value. The previous value associated with 196 * {@code key} (or zero, if there is none) is passed as the first argument to {@code 197 * accumulatorFunction}, and {@code x} is passed as the second argument. 198 * 199 * @since 21.0 200 */ 201 @CanIgnoreReturnValue 202 public long getAndAccumulate(K key, long x, LongBinaryOperator accumulatorFunction) { 203 checkNotNull(accumulatorFunction); 204 return getAndUpdate(key, oldValue -> accumulatorFunction.applyAsLong(oldValue, x)); 205 } 206 207 /** 208 * Associates {@code newValue} with {@code key} in this map, and returns the value previously 209 * associated with {@code key}, or zero if there was no such value. 210 */ 211 @CanIgnoreReturnValue 212 public long put(K key, long newValue) { 213 return getAndUpdate(key, x -> newValue); 214 } 215 216 /** 217 * Copies all of the mappings from the specified map to this map. The effect of this call is 218 * equivalent to that of calling {@code put(k, v)} on this map once for each mapping from key 219 * {@code k} to value {@code v} in the specified map. The behavior of this operation is undefined 220 * if the specified map is modified while the operation is in progress. 221 */ 222 public void putAll(Map<? extends K, ? extends Long> m) { 223 m.forEach(this::put); 224 } 225 226 /** 227 * Removes and returns the value associated with {@code key}. If {@code key} is not in the map, 228 * this method has no effect and returns zero. 229 */ 230 @CanIgnoreReturnValue 231 public long remove(K key) { 232 Long result = map.remove(key); 233 return (result == null) ? 0L : result.longValue(); 234 } 235 236 /** 237 * If {@code (key, value)} is currently in the map, this method removes it and returns true; 238 * otherwise, this method returns false. 239 */ 240 boolean remove(K key, long value) { 241 return map.remove(key, value); 242 } 243 244 /** 245 * Atomically remove {@code key} from the map iff its associated value is 0. 246 * 247 * @since 20.0 248 */ 249 @CanIgnoreReturnValue 250 public boolean removeIfZero(K key) { 251 return remove(key, 0); 252 } 253 254 /** 255 * Removes all mappings from this map whose values are zero. 256 * 257 * <p>This method is not atomic: the map may be visible in intermediate states, where some of the 258 * zero values have been removed and others have not. 259 */ 260 public void removeAllZeros() { 261 map.values().removeIf(x -> x == 0); 262 } 263 264 /** 265 * Returns the sum of all values in this map. 266 * 267 * <p>This method is not atomic: the sum may or may not include other concurrent operations. 268 */ 269 public long sum() { 270 return map.values().stream().mapToLong(Long::longValue).sum(); 271 } 272 273 @LazyInit @CheckForNull private transient Map<K, Long> asMap; 274 275 /** Returns a live, read-only view of the map backing this {@code AtomicLongMap}. */ 276 public Map<K, Long> asMap() { 277 Map<K, Long> result = asMap; 278 return (result == null) ? asMap = createAsMap() : result; 279 } 280 281 private Map<K, Long> createAsMap() { 282 return Collections.unmodifiableMap(map); 283 } 284 285 /** Returns true if this map contains a mapping for the specified key. */ 286 public boolean containsKey(Object key) { 287 return map.containsKey(key); 288 } 289 290 /** 291 * Returns the number of key-value mappings in this map. If the map contains more than {@code 292 * Integer.MAX_VALUE} elements, returns {@code Integer.MAX_VALUE}. 293 */ 294 public int size() { 295 return map.size(); 296 } 297 298 /** Returns {@code true} if this map contains no key-value mappings. */ 299 public boolean isEmpty() { 300 return map.isEmpty(); 301 } 302 303 /** 304 * Removes all of the mappings from this map. The map will be empty after this call returns. 305 * 306 * <p>This method is not atomic: the map may not be empty after returning if there were concurrent 307 * writes. 308 */ 309 public void clear() { 310 map.clear(); 311 } 312 313 @Override 314 public String toString() { 315 return map.toString(); 316 } 317 318 /** 319 * If {@code key} is not already associated with a value or if {@code key} is associated with 320 * zero, associate it with {@code newValue}. Returns the previous value associated with {@code 321 * key}, or zero if there was no mapping for {@code key}. 322 */ 323 long putIfAbsent(K key, long newValue) { 324 AtomicBoolean noValue = new AtomicBoolean(false); 325 Long result = 326 map.compute( 327 key, 328 (k, oldValue) -> { 329 if (oldValue == null || oldValue == 0) { 330 noValue.set(true); 331 return newValue; 332 } else { 333 return oldValue; 334 } 335 }); 336 return noValue.get() ? 0L : requireNonNull(result).longValue(); 337 } 338 339 /** 340 * If {@code (key, expectedOldValue)} is currently in the map, this method replaces {@code 341 * expectedOldValue} with {@code newValue} and returns true; otherwise, this method returns false. 342 * 343 * <p>If {@code expectedOldValue} is zero, this method will succeed if {@code (key, zero)} is 344 * currently in the map, or if {@code key} is not in the map at all. 345 */ 346 boolean replace(K key, long expectedOldValue, long newValue) { 347 if (expectedOldValue == 0L) { 348 return putIfAbsent(key, newValue) == 0L; 349 } else { 350 return map.replace(key, expectedOldValue, newValue); 351 } 352 } 353}