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