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