001/* 002 * Copyright (C) 2007 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.collect; 018 019import static com.google.common.base.Preconditions.checkArgument; 020import static com.google.common.base.Preconditions.checkNotNull; 021import static com.google.common.base.Preconditions.checkState; 022import static com.google.common.collect.CollectPreconditions.checkNonnegative; 023import static com.google.common.collect.CollectPreconditions.checkPositive; 024import static com.google.common.collect.Collections2.safeContains; 025import static com.google.common.collect.Collections2.safeRemove; 026import static com.google.common.collect.Lists.newArrayListWithExpectedSize; 027import static com.google.common.collect.Maps.safeGet; 028import static java.lang.Math.max; 029import static java.util.Objects.requireNonNull; 030 031import com.google.common.annotations.GwtIncompatible; 032import com.google.common.annotations.J2ktIncompatible; 033import com.google.common.primitives.Ints; 034import com.google.errorprone.annotations.CanIgnoreReturnValue; 035import com.google.j2objc.annotations.WeakOuter; 036import java.io.IOException; 037import java.io.ObjectInputStream; 038import java.io.ObjectOutputStream; 039import java.io.Serializable; 040import java.util.Collection; 041import java.util.Iterator; 042import java.util.List; 043import java.util.Map; 044import java.util.Set; 045import java.util.concurrent.ConcurrentHashMap; 046import java.util.concurrent.ConcurrentMap; 047import java.util.concurrent.atomic.AtomicInteger; 048import org.jspecify.annotations.Nullable; 049 050/** 051 * A multiset that supports concurrent modifications and that provides atomic versions of most 052 * {@code Multiset} operations (exceptions where noted). Null elements are not supported. 053 * 054 * <p>See the Guava User Guide article on <a href= 055 * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multiset">{@code Multiset}</a>. 056 * 057 * @author Cliff L. Biffle 058 * @author mike nonemacher 059 * @since 2.0 060 */ 061@J2ktIncompatible 062@GwtIncompatible 063public final class ConcurrentHashMultiset<E> extends AbstractMultiset<E> implements Serializable { 064 065 /* 066 * The ConcurrentHashMultiset's atomic operations are implemented primarily in terms of 067 * AtomicInteger's atomic operations, with some help from ConcurrentMap's atomic operations on 068 * creation and removal (including automatic removal of zeroes). If the modification of an 069 * AtomicInteger results in zero, we compareAndSet the value to zero; if that succeeds, we remove 070 * the entry from the Map. If another operation sees a zero in the map, it knows that the entry is 071 * about to be removed, so this operation may remove it (often by replacing it with a new 072 * AtomicInteger). 073 */ 074 075 /** The number of occurrences of each element. */ 076 private final transient ConcurrentMap<E, AtomicInteger> countMap; 077 078 /** 079 * An instance created in {@link #readObject} to be returned from {@link #readResolve}. This field 080 * is used only by those methods, and it is never set in a "normal" instance. 081 * 082 * <p>This class needs to write deserialized data into fields that are {@code final transient}. 083 * Such writes will become impossible to perform in {@link #readObject} after JEP 500. Instead, we 084 * must create a new instance with the desired field values, stash it in this field, and then 085 * instruct Java serialization to use it instead of the originally created object. 086 * 087 * <p>We have chosen this approach over at least two alternatives: 088 * 089 * <ul> 090 * <li>We could change the serialization of this class incompatibly. We have reserved the right 091 * to make such changes to our serialized forms, and we have made them before, usually 092 * without trouble. In this case, my guess is that our chosen approach is even less likely 093 * to lead to trouble than an incompatible change would be. 094 * <li>We could make {@link #countMap} no longer be {@code final}. Then we could write to it 095 * directly during deserialization. However, we would lose Java's guarantees for {@code 096 * final} fields, including that their values are guaranteed to be visible even when an 097 * instance is unsafely published. 098 * </ul> 099 */ 100 private transient @Nullable ConcurrentHashMultiset<E> deserializationReplacement; 101 102 /** 103 * Creates a new, empty {@code ConcurrentHashMultiset} using the default initial capacity, load 104 * factor, and concurrency settings. 105 */ 106 public static <E> ConcurrentHashMultiset<E> create() { 107 return create(new ConcurrentHashMap<>()); 108 } 109 110 /** 111 * Creates a new {@code ConcurrentHashMultiset} containing the specified elements, using the 112 * default initial capacity, load factor, and concurrency settings. 113 * 114 * <p>This implementation is highly efficient when {@code elements} is itself a {@link Multiset}. 115 * 116 * @param elements the elements that the multiset should contain 117 */ 118 public static <E> ConcurrentHashMultiset<E> create(Iterable<? extends E> elements) { 119 ConcurrentHashMultiset<E> multiset = create(); 120 Iterables.addAll(multiset, elements); 121 return multiset; 122 } 123 124 /** 125 * Creates a new, empty {@code ConcurrentHashMultiset} using {@code countMap} as the internal 126 * backing map. 127 * 128 * <p>This instance will assume ownership of {@code countMap}, and other code should not maintain 129 * references to the map or modify it in any way. 130 * 131 * <p>The returned multiset is serializable if the input map is. 132 * 133 * @param countMap backing map for storing the elements in the multiset and their counts. It must 134 * be empty. 135 * @throws IllegalArgumentException if {@code countMap} is not empty 136 * @since 20.0 137 */ 138 public static <E> ConcurrentHashMultiset<E> create(ConcurrentMap<E, AtomicInteger> countMap) { 139 checkArgument(countMap.isEmpty(), "the backing map (%s) must be empty", countMap); 140 return new ConcurrentHashMultiset<>(countMap); 141 } 142 143 private ConcurrentHashMultiset(ConcurrentMap<E, AtomicInteger> countMap) { 144 this.countMap = countMap; 145 } 146 147 // Query Operations 148 149 /** 150 * Returns the number of occurrences of {@code element} in this multiset. 151 * 152 * @param element the element to look for 153 * @return the nonnegative number of occurrences of the element 154 */ 155 @Override 156 public int count(@Nullable Object element) { 157 AtomicInteger existingCounter = safeGet(countMap, element); 158 return (existingCounter == null) ? 0 : existingCounter.get(); 159 } 160 161 /** 162 * {@inheritDoc} 163 * 164 * <p>If the data in the multiset is modified by any other threads during this method, it is 165 * undefined which (if any) of these modifications will be reflected in the result. 166 */ 167 @Override 168 public int size() { 169 long sum = 0L; 170 for (AtomicInteger value : countMap.values()) { 171 sum += value.get(); 172 } 173 return Ints.saturatedCast(sum); 174 } 175 176 /* 177 * We override the toArray methods for two reasons: 178 * 179 * 1. Both superclass toArray methods assume that size() gives a correct answer, while our size() 180 * might not (and the answer might change while we're building the array). 181 * 182 * TODO(cpovirk): Is this an issue anywhere anymore? It looks to have been fixed for Java 8 183 * (https://bugs.openjdk.org/browse/JDK-7121314) and before Lollipop 184 * (https://r.android.com/47508). We *would* need to worry for J2KT, whose own concurrency support 185 * is evolving (b/381065164, b/458160722), but this class is @J2ktIncompatible. 186 * 187 * 2. The superclass toArray() method declares the more general return type `@Nullable Object[]`, 188 * but we know that our values will never be `null`. 189 */ 190 191 @Override 192 public Object[] toArray() { 193 return snapshotElementsToList().toArray(); 194 } 195 196 @Override 197 @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations 198 public <T extends @Nullable Object> T[] toArray(T[] array) { 199 return snapshotElementsToList().toArray(array); 200 } 201 202 /* 203 * We'd love to use 'new ArrayList(this)' or 'list.addAll(this)', but 204 * either of these would recurse back to us again! 205 */ 206 private List<E> snapshotElementsToList() { 207 List<E> list = newArrayListWithExpectedSize(size()); 208 for (Entry<E> entry : entrySet()) { 209 E element = entry.getElement(); 210 for (int i = entry.getCount(); i > 0; i--) { 211 list.add(element); 212 } 213 } 214 return list; 215 } 216 217 // Modification Operations 218 219 /** 220 * Adds a number of occurrences of the specified element to this multiset. 221 * 222 * @param element the element to add 223 * @param occurrences the number of occurrences to add 224 * @return the previous count of the element before the operation; possibly zero 225 * @throws IllegalArgumentException if {@code occurrences} is negative, or if the resulting amount 226 * would exceed {@link Integer#MAX_VALUE} 227 */ 228 @CanIgnoreReturnValue 229 @Override 230 public int add(E element, int occurrences) { 231 checkNotNull(element); 232 if (occurrences == 0) { 233 return count(element); 234 } 235 checkPositive(occurrences, "occurrences"); 236 237 while (true) { 238 AtomicInteger existingCounter = safeGet(countMap, element); 239 if (existingCounter == null) { 240 existingCounter = countMap.putIfAbsent(element, new AtomicInteger(occurrences)); 241 if (existingCounter == null) { 242 return 0; 243 } 244 // existingCounter != null: fall through to operate against the existing AtomicInteger 245 } 246 247 while (true) { 248 int oldValue = existingCounter.get(); 249 if (oldValue != 0) { 250 try { 251 int newValue = Math.addExact(oldValue, occurrences); 252 if (existingCounter.compareAndSet(oldValue, newValue)) { 253 // newValue can't == 0, so no need to check & remove 254 return oldValue; 255 } 256 } catch (ArithmeticException overflow) { 257 throw new IllegalArgumentException( 258 "Overflow adding " + occurrences + " occurrences to a count of " + oldValue); 259 } 260 } else { 261 // In the case of a concurrent remove, we might observe a zero value, which means another 262 // thread is about to remove (element, existingCounter) from the map. Rather than wait, 263 // we can just do that work here. 264 AtomicInteger newCounter = new AtomicInteger(occurrences); 265 if ((countMap.putIfAbsent(element, newCounter) == null) 266 || countMap.replace(element, existingCounter, newCounter)) { 267 return 0; 268 } 269 break; 270 } 271 } 272 273 // If we're still here, there was a race, so just try again. 274 } 275 } 276 277 /** 278 * Removes a number of occurrences of the specified element from this multiset. If the multiset 279 * contains fewer than this number of occurrences to begin with, all occurrences will be removed. 280 * 281 * @param element the element whose occurrences should be removed 282 * @param occurrences the number of occurrences of the element to remove 283 * @return the count of the element before the operation; possibly zero 284 * @throws IllegalArgumentException if {@code occurrences} is negative 285 */ 286 /* 287 * TODO(cpovirk): remove and removeExactly currently accept null inputs only 288 * if occurrences == 0. This satisfies both NullPointerTester and 289 * CollectionRemoveTester.testRemove_nullAllowed, but it's not clear that it's 290 * a good policy, especially because, in order for the test to pass, the 291 * parameter must be misleadingly annotated as @Nullable. I suspect that 292 * we'll want to remove @Nullable, add an eager checkNotNull, and loosen up 293 * testRemove_nullAllowed. 294 */ 295 @CanIgnoreReturnValue 296 @Override 297 public int remove(@Nullable Object element, int occurrences) { 298 if (occurrences == 0) { 299 return count(element); 300 } 301 checkPositive(occurrences, "occurrences"); 302 303 AtomicInteger existingCounter = safeGet(countMap, element); 304 if (existingCounter == null) { 305 return 0; 306 } 307 while (true) { 308 int oldValue = existingCounter.get(); 309 if (oldValue != 0) { 310 int newValue = max(0, oldValue - occurrences); 311 if (existingCounter.compareAndSet(oldValue, newValue)) { 312 if (newValue == 0) { 313 // Just CASed to 0; remove the entry to clean up the map. If the removal fails, 314 // another thread has already replaced it with a new counter, which is fine. 315 countMap.remove(element, existingCounter); 316 } 317 return oldValue; 318 } 319 } else { 320 return 0; 321 } 322 } 323 } 324 325 /** 326 * Removes exactly the specified number of occurrences of {@code element}, or makes no change if 327 * this is not possible. 328 * 329 * <p>This method, in contrast to {@link #remove(Object, int)}, has no effect when the element 330 * count is smaller than {@code occurrences}. 331 * 332 * @param element the element to remove 333 * @param occurrences the number of occurrences of {@code element} to remove 334 * @return {@code true} if the removal was possible (including if {@code occurrences} is zero) 335 * @throws IllegalArgumentException if {@code occurrences} is negative 336 */ 337 @CanIgnoreReturnValue 338 public boolean removeExactly(@Nullable Object element, int occurrences) { 339 if (occurrences == 0) { 340 return true; 341 } 342 checkPositive(occurrences, "occurrences"); 343 344 AtomicInteger existingCounter = safeGet(countMap, element); 345 if (existingCounter == null) { 346 return false; 347 } 348 while (true) { 349 int oldValue = existingCounter.get(); 350 if (oldValue < occurrences) { 351 return false; 352 } 353 int newValue = oldValue - occurrences; 354 if (existingCounter.compareAndSet(oldValue, newValue)) { 355 if (newValue == 0) { 356 // Just CASed to 0; remove the entry to clean up the map. If the removal fails, 357 // another thread has already replaced it with a new counter, which is fine. 358 countMap.remove(element, existingCounter); 359 } 360 return true; 361 } 362 } 363 } 364 365 /** 366 * Adds or removes occurrences of {@code element} such that the {@link #count} of the element 367 * becomes {@code count}. 368 * 369 * @return the count of {@code element} in the multiset before this call 370 * @throws IllegalArgumentException if {@code count} is negative 371 */ 372 @CanIgnoreReturnValue 373 @Override 374 public int setCount(E element, int count) { 375 checkNotNull(element); 376 checkNonnegative(count, "count"); 377 while (true) { 378 AtomicInteger existingCounter = safeGet(countMap, element); 379 if (existingCounter == null) { 380 if (count == 0) { 381 return 0; 382 } else { 383 existingCounter = countMap.putIfAbsent(element, new AtomicInteger(count)); 384 if (existingCounter == null) { 385 return 0; 386 } 387 // existingCounter != null: fall through 388 } 389 } 390 391 while (true) { 392 int oldValue = existingCounter.get(); 393 if (oldValue == 0) { 394 if (count == 0) { 395 return 0; 396 } else { 397 AtomicInteger newCounter = new AtomicInteger(count); 398 if ((countMap.putIfAbsent(element, newCounter) == null) 399 || countMap.replace(element, existingCounter, newCounter)) { 400 return 0; 401 } 402 } 403 break; 404 } else { 405 if (existingCounter.compareAndSet(oldValue, count)) { 406 if (count == 0) { 407 // Just CASed to 0; remove the entry to clean up the map. If the removal fails, 408 // another thread has already replaced it with a new counter, which is fine. 409 countMap.remove(element, existingCounter); 410 } 411 return oldValue; 412 } 413 } 414 } 415 } 416 } 417 418 /** 419 * Sets the number of occurrences of {@code element} to {@code newCount}, but only if the count is 420 * currently {@code expectedOldCount}. If {@code element} does not appear in the multiset exactly 421 * {@code expectedOldCount} times, no changes will be made. 422 * 423 * @return {@code true} if the change was successful. This usually indicates that the multiset has 424 * been modified, but not always: in the case that {@code expectedOldCount == newCount}, the 425 * method will return {@code true} if the condition was met. 426 * @throws IllegalArgumentException if {@code expectedOldCount} or {@code newCount} is negative 427 */ 428 @CanIgnoreReturnValue 429 @Override 430 public boolean setCount(E element, int expectedOldCount, int newCount) { 431 checkNotNull(element); 432 checkNonnegative(expectedOldCount, "oldCount"); 433 checkNonnegative(newCount, "newCount"); 434 435 AtomicInteger existingCounter = safeGet(countMap, element); 436 if (existingCounter == null) { 437 if (expectedOldCount != 0) { 438 return false; 439 } else if (newCount == 0) { 440 return true; 441 } else { 442 // if our write lost the race, it must have lost to a nonzero value, so we can stop 443 return countMap.putIfAbsent(element, new AtomicInteger(newCount)) == null; 444 } 445 } 446 int oldValue = existingCounter.get(); 447 if (oldValue == expectedOldCount) { 448 if (oldValue == 0) { 449 if (newCount == 0) { 450 // Just observed a 0; try to remove the entry to clean up the map 451 countMap.remove(element, existingCounter); 452 return true; 453 } else { 454 AtomicInteger newCounter = new AtomicInteger(newCount); 455 return (countMap.putIfAbsent(element, newCounter) == null) 456 || countMap.replace(element, existingCounter, newCounter); 457 } 458 } else { 459 if (existingCounter.compareAndSet(oldValue, newCount)) { 460 if (newCount == 0) { 461 // Just CASed to 0; remove the entry to clean up the map. If the removal fails, 462 // another thread has already replaced it with a new counter, which is fine. 463 countMap.remove(element, existingCounter); 464 } 465 return true; 466 } 467 } 468 } 469 return false; 470 } 471 472 // Views 473 474 @Override 475 Set<E> createElementSet() { 476 Set<E> delegate = countMap.keySet(); 477 return new ForwardingSet<E>() { 478 @Override 479 protected Set<E> delegate() { 480 return delegate; 481 } 482 483 @Override 484 public boolean contains(@Nullable Object object) { 485 return object != null && safeContains(delegate, object); 486 } 487 488 @Override 489 public boolean containsAll(Collection<?> collection) { 490 return standardContainsAll(collection); 491 } 492 493 @Override 494 public boolean remove(@Nullable Object object) { 495 return object != null && safeRemove(delegate, object); 496 } 497 498 @Override 499 public boolean removeAll(Collection<?> c) { 500 return standardRemoveAll(c); 501 } 502 }; 503 } 504 505 @Override 506 Iterator<E> elementIterator() { 507 throw new AssertionError("should never be called"); 508 } 509 510 /** 511 * @deprecated Internal method, use {@link #entrySet()}. 512 */ 513 @Deprecated 514 @Override 515 public Set<Entry<E>> createEntrySet() { 516 return new EntrySet(); 517 } 518 519 @Override 520 int distinctElements() { 521 return countMap.size(); 522 } 523 524 @Override 525 public boolean isEmpty() { 526 return countMap.isEmpty(); 527 } 528 529 @Override 530 Iterator<Entry<E>> entryIterator() { 531 // AbstractIterator makes this fairly clean, but it doesn't support remove(). To support 532 // remove(), we create an AbstractIterator, and then use ForwardingIterator to delegate to it. 533 Iterator<Entry<E>> readOnlyIterator = 534 new AbstractIterator<Entry<E>>() { 535 private final Iterator<Map.Entry<E, AtomicInteger>> mapEntries = 536 countMap.entrySet().iterator(); 537 538 @Override 539 protected @Nullable Entry<E> computeNext() { 540 while (true) { 541 if (!mapEntries.hasNext()) { 542 return endOfData(); 543 } 544 Map.Entry<E, AtomicInteger> mapEntry = mapEntries.next(); 545 int count = mapEntry.getValue().get(); 546 if (count != 0) { 547 return Multisets.immutableEntry(mapEntry.getKey(), count); 548 } 549 } 550 } 551 }; 552 553 return new ForwardingIterator<Entry<E>>() { 554 private @Nullable Entry<E> last; 555 556 @Override 557 protected Iterator<Entry<E>> delegate() { 558 return readOnlyIterator; 559 } 560 561 @Override 562 public Entry<E> next() { 563 last = super.next(); 564 return last; 565 } 566 567 @Override 568 public void remove() { 569 checkState(last != null, "no calls to next() since the last call to remove()"); 570 ConcurrentHashMultiset.this.setCount(last.getElement(), 0); 571 last = null; 572 } 573 }; 574 } 575 576 @Override 577 public Iterator<E> iterator() { 578 return Multisets.iteratorImpl(this); 579 } 580 581 @Override 582 public void clear() { 583 countMap.clear(); 584 } 585 586 @WeakOuter 587 private final class EntrySet extends AbstractMultiset<E>.EntrySet { 588 @Override 589 ConcurrentHashMultiset<E> multiset() { 590 return ConcurrentHashMultiset.this; 591 } 592 593 /* 594 * Note: the superclass toArray() methods assume that size() gives a correct 595 * answer, which ours does not. 596 */ 597 598 @Override 599 public Object[] toArray() { 600 return snapshot().toArray(); 601 } 602 603 @Override 604 @SuppressWarnings("nullness") // b/192354773 in our checker affects toArray declarations 605 public <T extends @Nullable Object> T[] toArray(T[] array) { 606 return snapshot().toArray(array); 607 } 608 609 private List<Multiset.Entry<E>> snapshot() { 610 List<Multiset.Entry<E>> list = newArrayListWithExpectedSize(size()); 611 // Not Iterables.addAll(list, this), because that'll forward right back here. 612 Iterators.addAll(list, iterator()); 613 return list; 614 } 615 } 616 617 /** 618 * @serialData the ConcurrentMap of elements and their counts. 619 */ 620 private void writeObject(ObjectOutputStream stream) throws IOException { 621 stream.defaultWriteObject(); 622 stream.writeObject(countMap); 623 } 624 625 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 626 stream.defaultReadObject(); 627 @SuppressWarnings("unchecked") // reading data stored by writeObject 628 ConcurrentMap<E, AtomicInteger> deserializedCountMap = 629 (ConcurrentMap<E, AtomicInteger>) requireNonNull(stream.readObject()); 630 deserializationReplacement = new ConcurrentHashMultiset<>(deserializedCountMap); 631 } 632 633 private Object readResolve() { 634 return requireNonNull(deserializationReplacement); // set by readObject 635 } 636 637 private static final long serialVersionUID = 1; 638}