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