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