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