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