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