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