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