48 |
|
import java.util.concurrent.ConcurrentMap; |
49 |
|
import java.util.concurrent.ConcurrentHashMap; |
50 |
|
import java.util.concurrent.ConcurrentSkipListMap; |
51 |
+ |
import java.util.concurrent.atomic.AtomicBoolean; |
52 |
|
import java.util.function.BiFunction; |
53 |
+ |
import java.util.function.Function; |
54 |
|
import java.util.function.Supplier; |
55 |
|
|
56 |
|
import org.testng.annotations.Test; |
57 |
|
import org.testng.annotations.DataProvider; |
58 |
+ |
import static java.util.Objects.requireNonNull; |
59 |
|
import static org.testng.Assert.fail; |
60 |
|
import static org.testng.Assert.assertEquals; |
61 |
|
import static org.testng.Assert.assertTrue; |
536 |
|
"Should throw NPE"); |
537 |
|
} |
538 |
|
|
539 |
+ |
/** A function that flipflops between running two other functions. */ |
540 |
+ |
static <T,U,V> BiFunction<T,U,V> twoStep(AtomicBoolean b, |
541 |
+ |
BiFunction<T,U,V> first, |
542 |
+ |
BiFunction<T,U,V> second) { |
543 |
+ |
return (t, u) -> { |
544 |
+ |
boolean bb = b.get(); |
545 |
+ |
try { |
546 |
+ |
return (b.get() ? first : second).apply(t, u); |
547 |
+ |
} finally { |
548 |
+ |
b.set(!bb); |
549 |
+ |
}}; |
550 |
+ |
} |
551 |
+ |
|
552 |
+ |
/** |
553 |
+ |
* Simulates races by modifying the map within the mapping function. |
554 |
+ |
*/ |
555 |
+ |
@Test |
556 |
+ |
public void testConcurrentMap_computeIfAbsent_racy() { |
557 |
+ |
final ConcurrentMap<Long,Long> map = new ImplementsConcurrentMap<>(); |
558 |
+ |
final Long two = 2L; |
559 |
+ |
Function<Long,Long> f, g; |
560 |
+ |
|
561 |
+ |
// race not detected if function returns null |
562 |
+ |
f = (k) -> { map.put(two, 42L); return null; }; |
563 |
+ |
assertNull(map.computeIfAbsent(two, f)); |
564 |
+ |
assertEquals(42L, (long)map.get(two)); |
565 |
+ |
|
566 |
+ |
map.clear(); |
567 |
+ |
f = (k) -> { map.put(two, 42L); return 86L; }; |
568 |
+ |
assertEquals(42L, (long)map.computeIfAbsent(two, f)); |
569 |
+ |
assertEquals(42L, (long)map.get(two)); |
570 |
+ |
|
571 |
+ |
// mapping function ignored if value already exists |
572 |
+ |
map.put(two, 99L); |
573 |
+ |
assertEquals(99L, (long)map.computeIfAbsent(two, f)); |
574 |
+ |
assertEquals(99L, (long)map.get(two)); |
575 |
+ |
} |
576 |
+ |
|
577 |
+ |
/** |
578 |
+ |
* Simulates races by modifying the map within the remapping function. |
579 |
+ |
*/ |
580 |
+ |
@Test |
581 |
+ |
public void testConcurrentMap_computeIfPresent_racy() { |
582 |
+ |
final AtomicBoolean b = new AtomicBoolean(true); |
583 |
+ |
final ConcurrentMap<Long,Long> map = new ImplementsConcurrentMap<>(); |
584 |
+ |
final Long two = 2L; |
585 |
+ |
BiFunction<Long,Long,Long> f, g; |
586 |
+ |
|
587 |
+ |
for (Long val : new Long[] { null, 86L }) { |
588 |
+ |
map.clear(); |
589 |
+ |
|
590 |
+ |
// Function not invoked if no mapping exists |
591 |
+ |
f = (k, v) -> { map.put(two, 42L); return val; }; |
592 |
+ |
assertNull(map.computeIfPresent(two, f)); |
593 |
+ |
assertNull(map.get(two)); |
594 |
+ |
|
595 |
+ |
map.put(two, 42L); |
596 |
+ |
f = (k, v) -> { map.put(two, 86L); return val; }; |
597 |
+ |
g = (k, v) -> { |
598 |
+ |
assertSame(two, k); |
599 |
+ |
assertEquals(86L, (long)v); |
600 |
+ |
return null; |
601 |
+ |
}; |
602 |
+ |
assertNull(map.computeIfPresent(two, twoStep(b, f, g))); |
603 |
+ |
assertFalse(map.containsKey(two)); |
604 |
+ |
assertTrue(b.get()); |
605 |
+ |
|
606 |
+ |
map.put(two, 42L); |
607 |
+ |
f = (k, v) -> { map.put(two, 86L); return val; }; |
608 |
+ |
g = (k, v) -> { |
609 |
+ |
assertSame(two, k); |
610 |
+ |
assertEquals(86L, (long)v); |
611 |
+ |
return 99L; |
612 |
+ |
}; |
613 |
+ |
assertEquals(99L, (long)map.computeIfPresent(two, twoStep(b, f, g))); |
614 |
+ |
assertTrue(map.containsKey(two)); |
615 |
+ |
assertTrue(b.get()); |
616 |
+ |
} |
617 |
+ |
} |
618 |
+ |
|
619 |
+ |
@Test |
620 |
+ |
public void testConcurrentMap_compute_simple() { |
621 |
+ |
final ConcurrentMap<Long,Long> map = new ImplementsConcurrentMap<>(); |
622 |
+ |
BiFunction<Long,Long,Long> fun = (k, v) -> ((v == null) ? 0L : k + v); |
623 |
+ |
assertEquals(Long.valueOf(0L), map.compute(3L, fun)); |
624 |
+ |
assertEquals(Long.valueOf(3L), map.compute(3L, fun)); |
625 |
+ |
assertEquals(Long.valueOf(6L), map.compute(3L, fun)); |
626 |
+ |
assertNull(map.compute(3L, (k, v) -> null)); |
627 |
+ |
assertTrue(map.isEmpty()); |
628 |
+ |
|
629 |
+ |
assertEquals(Long.valueOf(0L), map.compute(new Long(3L), fun)); |
630 |
+ |
assertEquals(Long.valueOf(3L), map.compute(new Long(3L), fun)); |
631 |
+ |
assertEquals(Long.valueOf(6L), map.compute(new Long(3L), fun)); |
632 |
+ |
assertNull(map.compute(3L, (k, v) -> null)); |
633 |
+ |
assertTrue(map.isEmpty()); |
634 |
+ |
} |
635 |
+ |
|
636 |
+ |
/** |
637 |
+ |
* Simulates races by modifying the map within the remapping function. |
638 |
+ |
*/ |
639 |
+ |
@Test |
640 |
+ |
public void testConcurrentMap_compute_racy() { |
641 |
+ |
final AtomicBoolean b = new AtomicBoolean(true); |
642 |
+ |
final ConcurrentMap<Long,Long> map = new ImplementsConcurrentMap<>(); |
643 |
+ |
final Long two = 2L; |
644 |
+ |
BiFunction<Long,Long,Long> f, g; |
645 |
+ |
|
646 |
+ |
// null -> null is a no-op; race not detected |
647 |
+ |
f = (k, v) -> { map.put(two, 42L); return null; }; |
648 |
+ |
assertNull(map.compute(two, f)); |
649 |
+ |
assertEquals(42L, (long)map.get(two)); |
650 |
+ |
|
651 |
+ |
for (Long val : new Long[] { null, 86L }) { |
652 |
+ |
map.clear(); |
653 |
+ |
|
654 |
+ |
f = (k, v) -> { map.put(two, 42L); return 86L; }; |
655 |
+ |
g = (k, v) -> { |
656 |
+ |
assertSame(two, k); |
657 |
+ |
assertEquals(42L, (long)v); |
658 |
+ |
return k + v; |
659 |
+ |
}; |
660 |
+ |
assertEquals(44L, (long)map.compute(two, twoStep(b, f, g))); |
661 |
+ |
assertEquals(44L, (long)map.get(two)); |
662 |
+ |
assertTrue(b.get()); |
663 |
+ |
|
664 |
+ |
f = (k, v) -> { map.remove(two); return val; }; |
665 |
+ |
g = (k, v) -> { |
666 |
+ |
assertSame(two, k); |
667 |
+ |
assertNull(v); |
668 |
+ |
return 44L; |
669 |
+ |
}; |
670 |
+ |
assertEquals(44L, (long)map.compute(two, twoStep(b, f, g))); |
671 |
+ |
assertEquals(44L, (long)map.get(two)); |
672 |
+ |
assertTrue(map.containsKey(two)); |
673 |
+ |
assertTrue(b.get()); |
674 |
+ |
|
675 |
+ |
f = (k, v) -> { map.remove(two); return val; }; |
676 |
+ |
g = (k, v) -> { |
677 |
+ |
assertSame(two, k); |
678 |
+ |
assertNull(v); |
679 |
+ |
return null; |
680 |
+ |
}; |
681 |
+ |
assertNull(map.compute(two, twoStep(b, f, g))); |
682 |
+ |
assertNull(map.get(two)); |
683 |
+ |
assertFalse(map.containsKey(two)); |
684 |
+ |
assertTrue(b.get()); |
685 |
+ |
} |
686 |
+ |
} |
687 |
+ |
|
688 |
+ |
/** |
689 |
+ |
* Simulates races by modifying the map within the remapping function. |
690 |
+ |
*/ |
691 |
+ |
@Test |
692 |
+ |
public void testConcurrentMap_merge_racy() { |
693 |
+ |
final AtomicBoolean b = new AtomicBoolean(true); |
694 |
+ |
final ConcurrentMap<Long,Long> map = new ImplementsConcurrentMap<>(); |
695 |
+ |
final Long two = 2L; |
696 |
+ |
BiFunction<Long,Long,Long> f, g; |
697 |
+ |
|
698 |
+ |
for (Long val : new Long[] { null, 86L }) { |
699 |
+ |
map.clear(); |
700 |
+ |
|
701 |
+ |
f = (v, w) -> { throw new AssertionError(); }; |
702 |
+ |
assertEquals(99L, (long)map.merge(two, 99L, f)); |
703 |
+ |
assertEquals(99L, (long)map.get(two)); |
704 |
+ |
|
705 |
+ |
f = (v, w) -> { map.put(two, 42L); return val; }; |
706 |
+ |
g = (v, w) -> { |
707 |
+ |
assertEquals(42L, (long)v); |
708 |
+ |
assertEquals(3L, (long)w); |
709 |
+ |
return v + w; |
710 |
+ |
}; |
711 |
+ |
assertEquals(45L, (long)map.merge(two, 3L, twoStep(b, f, g))); |
712 |
+ |
assertEquals(45L, (long)map.get(two)); |
713 |
+ |
assertTrue(b.get()); |
714 |
+ |
|
715 |
+ |
f = (v, w) -> { map.remove(two); return val; }; |
716 |
+ |
g = (k, v) -> { throw new AssertionError(); }; |
717 |
+ |
assertEquals(55L, (long)map.merge(two, 55L, twoStep(b, f, g))); |
718 |
+ |
assertEquals(55L, (long)map.get(two)); |
719 |
+ |
assertTrue(map.containsKey(two)); |
720 |
+ |
assertFalse(b.get()); b.set(true); |
721 |
+ |
} |
722 |
+ |
} |
723 |
+ |
|
724 |
|
public enum IntegerEnum { |
725 |
|
|
726 |
|
e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, |
1026 |
|
|
1027 |
|
protected ExtendsAbstractMap(M map) { this.map = map; } |
1028 |
|
|
1029 |
< |
public Set<Map.Entry<K,V>> entrySet() { |
1029 |
> |
@Override public Set<Map.Entry<K,V>> entrySet() { |
1030 |
|
return new AbstractSet<Map.Entry<K,V>>() { |
1031 |
< |
public int size() { |
1031 |
> |
@Override public int size() { |
1032 |
|
return map.size(); |
1033 |
|
} |
1034 |
|
|
1035 |
< |
public Iterator<Map.Entry<K,V>> iterator() { |
1035 |
> |
@Override public Iterator<Map.Entry<K,V>> iterator() { |
1036 |
|
final Iterator<Map.Entry<K,V>> source = map.entrySet().iterator(); |
1037 |
|
return new Iterator<Map.Entry<K,V>>() { |
1038 |
|
public boolean hasNext() { return source.hasNext(); } |
1041 |
|
}; |
1042 |
|
} |
1043 |
|
|
1044 |
< |
public boolean add(Map.Entry<K,V> e) { |
1044 |
> |
@Override public boolean add(Map.Entry<K,V> e) { |
1045 |
|
return map.entrySet().add(e); |
1046 |
|
} |
1047 |
|
}; |
1048 |
|
} |
1049 |
|
|
1050 |
< |
public V put(K key, V value) { |
1050 |
> |
@Override public V put(K key, V value) { |
1051 |
|
return map.put(key, value); |
1052 |
|
} |
1053 |
|
} |
1054 |
|
|
1055 |
|
/** |
1056 |
|
* A simple mutable concurrent map implementation that provides only default |
1057 |
< |
* implementations of all methods. ie. none of the ConcurrentMap interface |
1057 |
> |
* implementations of all methods, i.e. none of the ConcurrentMap interface |
1058 |
|
* default methods have overridden implementations. |
1059 |
|
* |
1060 |
|
* @param <K> Type of keys |
1063 |
|
public static class ImplementsConcurrentMap<K,V> extends ExtendsAbstractMap<ConcurrentMap<K,V>, K, V> implements ConcurrentMap<K,V> { |
1064 |
|
public ImplementsConcurrentMap() { super(new ConcurrentHashMap<K,V>()); } |
1065 |
|
|
1066 |
< |
// ConcurrentMap reabstracts these methods |
1066 |
> |
// ConcurrentMap reabstracts these methods. |
1067 |
> |
// |
1068 |
> |
// Unlike ConcurrentHashMap, we have zero tolerance for null values. |
1069 |
|
|
1070 |
< |
public V replace(K k, V v) { return map.replace(k, v); }; |
1070 |
> |
@Override public V replace(K k, V v) { |
1071 |
> |
return map.replace(requireNonNull(k), requireNonNull(v)); |
1072 |
> |
} |
1073 |
|
|
1074 |
< |
public boolean replace(K k, V v, V vv) { return map.replace(k, v, vv); }; |
1074 |
> |
@Override public boolean replace(K k, V v, V vv) { |
1075 |
> |
return map.replace(requireNonNull(k), |
1076 |
> |
requireNonNull(v), |
1077 |
> |
requireNonNull(vv)); |
1078 |
> |
} |
1079 |
|
|
1080 |
< |
public boolean remove(Object k, Object v) { return map.remove(k, v); } |
1080 |
> |
@Override public boolean remove(Object k, Object v) { |
1081 |
> |
return map.remove(requireNonNull(k), requireNonNull(v)); |
1082 |
> |
} |
1083 |
|
|
1084 |
< |
public V putIfAbsent(K k, V v) { return map.putIfAbsent(k, v); } |
1084 |
> |
@Override public V putIfAbsent(K k, V v) { |
1085 |
> |
return map.putIfAbsent(requireNonNull(k), requireNonNull(v)); |
1086 |
> |
} |
1087 |
|
} |
1088 |
|
} |