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