diff --git a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java --- a/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java +++ b/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java @@ -623,8 +623,8 @@ final CompletableFuture tryFire(int mode) { CompletableFuture d; CompletableFuture a; Object r; Throwable x; Function f; - if ((d = dep) == null || (f = fn) == null - || (a = src) == null || (r = a.result) == null) + if ((a = src) == null || (r = a.result) == null + || (d = dep) == null || (f = fn) == null) return null; tryComplete: if (d.result == null) { if (r instanceof AltResult) { @@ -645,7 +645,7 @@ d.completeThrowable(ex); } } - dep = null; src = null; fn = null; + src = null; dep = null; fn = null; return d.postFire(a, mode); } } @@ -695,8 +695,8 @@ final CompletableFuture tryFire(int mode) { CompletableFuture d; CompletableFuture a; Object r; Throwable x; Consumer f; - if ((d = dep) == null || (f = fn) == null - || (a = src) == null || (r = a.result) == null) + if ((a = src) == null || (r = a.result) == null + || (d = dep) == null || (f = fn) == null) return null; tryComplete: if (d.result == null) { if (r instanceof AltResult) { @@ -718,7 +718,7 @@ d.completeThrowable(ex); } } - dep = null; src = null; fn = null; + src = null; dep = null; fn = null; return d.postFire(a, mode); } } @@ -769,8 +769,8 @@ final CompletableFuture tryFire(int mode) { CompletableFuture d; CompletableFuture a; Object r; Throwable x; Runnable f; - if ((d = dep) == null || (f = fn) == null - || (a = src) == null || (r = a.result) == null) + if ((a = src) == null || (r = a.result) == null + || (d = dep) == null || (f = fn) == null) return null; if (d.result == null) { if (r instanceof AltResult && (x = ((AltResult)r).ex) != null) @@ -787,7 +787,7 @@ d.completeThrowable(ex); } } - dep = null; src = null; fn = null; + src = null; dep = null; fn = null; return d.postFire(a, mode); } } @@ -832,11 +832,11 @@ final CompletableFuture tryFire(int mode) { CompletableFuture d; CompletableFuture a; Object r; BiConsumer f; - if ((d = dep) == null || (f = fn) == null - || (a = src) == null || (r = a.result) == null + if ((a = src) == null || (r = a.result) == null + || (d = dep) == null || (f = fn) == null || !d.uniWhenComplete(r, f, mode > 0 ? null : this)) return null; - dep = null; src = null; fn = null; + src = null; dep = null; fn = null; return d.postFire(a, mode); } } @@ -902,11 +902,11 @@ final CompletableFuture tryFire(int mode) { CompletableFuture d; CompletableFuture a; Object r; BiFunction f; - if ((d = dep) == null || (f = fn) == null - || (a = src) == null || (r = a.result) == null + if ((a = src) == null || (r = a.result) == null + || (d = dep) == null || (f = fn) == null || !d.uniHandle(r, f, mode > 0 ? null : this)) return null; - dep = null; src = null; fn = null; + src = null; dep = null; fn = null; return d.postFire(a, mode); } } @@ -965,11 +965,11 @@ final CompletableFuture tryFire(int mode) { CompletableFuture d; CompletableFuture a; Object r; Function f; - if ((d = dep) == null || (f = fn) == null - || (a = src) == null || (r = a.result) == null + if ((a = src) == null || (r = a.result) == null + || (d = dep) == null || (f = fn) == null || !d.uniExceptionally(r, f, mode > 0 ? null : this)) return null; - dep = null; src = null; fn = null; + src = null; dep = null; fn = null; return d.postFire(a, mode); } } @@ -1024,8 +1024,8 @@ CompletableFuture d; CompletableFuture a; Function> f; Object r; Throwable x; - if ((d = dep) == null || (f = fn) == null - || (a = src) == null || (r = a.result) == null) + if ((a = src) == null || (r = a.result) == null + || (d = dep) == null || (f = fn) == null) return null; if (d.result == null) { if ((r instanceof AltResult) && @@ -1048,7 +1048,7 @@ else d.internalComplete(r); } - dep = null; src = null; fn = null; + src = null; dep = null; fn = null; return d.postFire(a, mode); } } @@ -1086,8 +1086,8 @@ } final CompletableFuture tryFire(int mode) { CompletableFuture d; CompletableFuture a; Object r; - if ((d = dep) == null - || (a = src) == null || (r = a.result) == null) + if ((a = src) == null || (r = a.result) == null + || (d = dep) == null) return null; if (d.result == null) d.completeRelay(r); @@ -1128,8 +1128,8 @@ CompletableFuture d; CompletableFuture a; Function> f; Object r; Throwable x; - if ((d = dep) == null || (f = fn) == null - || (a = src) == null || (r = a.result) == null) + if ((a = src) == null || (r = a.result) == null + || (d = dep) == null || (f = fn) == null) return null; tryComplete: if (d.result == null) { if (r instanceof AltResult) { @@ -1155,7 +1155,7 @@ d.completeThrowable(ex); } } - dep = null; src = null; fn = null; + src = null; dep = null; fn = null; return d.postFire(a, mode); } } @@ -1270,12 +1270,12 @@ CompletableFuture a; CompletableFuture b; Object r, s; BiFunction f; - if ((d = dep) == null || (f = fn) == null - || (a = src) == null || (r = a.result) == null + if ( (a = src) == null || (r = a.result) == null || (b = snd) == null || (s = b.result) == null + || (d = dep) == null || (f = fn) == null || !d.biApply(r, s, f, mode > 0 ? null : this)) return null; - dep = null; src = null; snd = null; fn = null; + src = null; snd = null; dep = null; fn = null; return d.postFire(a, b, mode); } } @@ -1345,12 +1345,12 @@ CompletableFuture a; CompletableFuture b; Object r, s; BiConsumer f; - if ((d = dep) == null || (f = fn) == null - || (a = src) == null || (r = a.result) == null + if ( (a = src) == null || (r = a.result) == null || (b = snd) == null || (s = b.result) == null + || (d = dep) == null || (f = fn) == null || !d.biAccept(r, s, f, mode > 0 ? null : this)) return null; - dep = null; src = null; snd = null; fn = null; + src = null; snd = null; dep = null; fn = null; return d.postFire(a, b, mode); } } @@ -1421,12 +1421,12 @@ CompletableFuture a; CompletableFuture b; Object r, s; Runnable f; - if ((d = dep) == null || (f = fn) == null - || (a = src) == null || (r = a.result) == null + if ( (a = src) == null || (r = a.result) == null || (b = snd) == null || (s = b.result) == null + || (d = dep) == null || (f = fn) == null || !d.biRun(r, s, f, mode > 0 ? null : this)) return null; - dep = null; src = null; snd = null; fn = null; + src = null; snd = null; dep = null; fn = null; return d.postFire(a, b, mode); } } @@ -1482,9 +1482,9 @@ CompletableFuture a; CompletableFuture b; Object r, s, z; Throwable x; - if ((d = dep) == null - || (a = src) == null || (r = a.result) == null - || (b = snd) == null || (s = b.result) == null) + if ( (a = src) == null || (r = a.result) == null + || (b = snd) == null || (s = b.result) == null + || (d = dep) == null) return null; if (d.result == null) { if ((r instanceof AltResult @@ -1557,13 +1557,11 @@ super(executor, dep, src, snd); this.fn = fn; } final CompletableFuture tryFire(int mode) { - CompletableFuture d; - CompletableFuture a; - CompletableFuture b; + CompletableFuture d; CompletableFuture a, b; Object r; Throwable x; Function f; - if ((d = dep) == null || (f = fn) == null - || (a = src) == null || (b = snd) == null - || ((r = a.result) == null && (r = b.result) == null)) + if ((a = src) == null || (b = snd) == null + || ((r = a.result) == null && (r = b.result) == null) + || (d = dep) == null || (f = fn) == null) return null; tryComplete: if (d.result == null) { try { @@ -1582,7 +1580,7 @@ d.completeThrowable(ex); } } - dep = null; src = null; snd = null; fn = null; + src = null; snd = null; dep = null; fn = null; return d.postFire(a, b, mode); } } @@ -1612,13 +1610,11 @@ super(executor, dep, src, snd); this.fn = fn; } final CompletableFuture tryFire(int mode) { - CompletableFuture d; - CompletableFuture a; - CompletableFuture b; + CompletableFuture d; CompletableFuture a, b; Object r; Throwable x; Consumer f; - if ((d = dep) == null || (f = fn) == null - || (a = src) == null || (b = snd) == null - || ((r = a.result) == null && (r = b.result) == null)) + if ((a = src) == null || (b = snd) == null + || ((r = a.result) == null && (r = b.result) == null) + || (d = dep) == null || (f = fn) == null) return null; tryComplete: if (d.result == null) { try { @@ -1638,7 +1634,7 @@ d.completeThrowable(ex); } } - dep = null; src = null; snd = null; fn = null; + src = null; snd = null; dep = null; fn = null; return d.postFire(a, b, mode); } } @@ -1668,13 +1664,11 @@ super(executor, dep, src, snd); this.fn = fn; } final CompletableFuture tryFire(int mode) { - CompletableFuture d; - CompletableFuture a; - CompletableFuture b; + CompletableFuture d; CompletableFuture a, b; Object r; Throwable x; Runnable f; - if ((d = dep) == null || (f = fn) == null - || (a = src) == null || (b = snd) == null - || ((r = a.result) == null && (r = b.result) == null)) + if ((a = src) == null || (b = snd) == null + || ((r = a.result) == null && (r = b.result) == null) + || (d = dep) == null || (f = fn) == null) return null; if (d.result == null) { try { @@ -1691,7 +1685,7 @@ d.completeThrowable(ex); } } - dep = null; src = null; snd = null; fn = null; + src = null; snd = null; dep = null; fn = null; return d.postFire(a, b, mode); } } @@ -1726,11 +1720,10 @@ CompletableFuture d; CompletableFuture a; CompletableFuture[] as; Object r; - if ((d = dep) == null - || (a = src) == null || (r = a.result) == null - || (as = srcs) == null) + if ((a = src) == null || (r = a.result) == null + || (d = dep) == null || (as = srcs) == null) return null; - dep = null; src = null; srcs = null; + src = null; dep = null; srcs = null; if (d.completeRelay(r)) { for (CompletableFuture b : as) if (b != a) diff --git a/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java b/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java --- a/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java +++ b/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java @@ -1667,11 +1667,14 @@ * If the specified key is not already associated with a value, * attempts to compute its value using the given mapping function * and enters it into this map unless {@code null}. The entire - * method invocation is performed atomically, so the function is - * applied at most once per key. Some attempted update operations - * on this map by other threads may be blocked while computation - * is in progress, so the computation should be short and simple, - * and must not attempt to update any other mappings of this map. + * method invocation is performed atomically. The supplied + * function is invoked exactly once per invocation of this method + * if the key is absent, else not at all. Some attempted update + * operations on this map by other threads may be blocked while + * computation is in progress, so the computation should be short + * and simple. + * + *

The mapping function must not modify this map during computation. * * @param key key with which the specified value is to be associated * @param mappingFunction the function to compute a value @@ -1778,10 +1781,13 @@ * If the value for the specified key is present, attempts to * compute a new mapping given the key and its current mapped * value. The entire method invocation is performed atomically. - * Some attempted update operations on this map by other threads - * may be blocked while computation is in progress, so the - * computation should be short and simple, and must not attempt to - * update any other mappings of this map. + * The supplied function is invoked exactly once per invocation of + * this method if the key is present, else not at all. Some + * attempted update operations on this map by other threads may be + * blocked while computation is in progress, so the computation + * should be short and simple. + * + *

The remapping function must not modify this map during computation. * * @param key key with which a value may be associated * @param remappingFunction the function to compute a value @@ -1870,10 +1876,12 @@ * Attempts to compute a mapping for the specified key and its * current mapped value (or {@code null} if there is no current * mapping). The entire method invocation is performed atomically. - * Some attempted update operations on this map by other threads - * may be blocked while computation is in progress, so the - * computation should be short and simple, and must not attempt to - * update any other mappings of this Map. + * The supplied function is invoked exactly once per invocation of + * this method. Some attempted update operations on this map by + * other threads may be blocked while computation is in progress, + * so the computation should be short and simple. + * + *

The remapping function must not modify this map during computation. * * @param key key with which the specified value is to be associated * @param remappingFunction the function to compute a value diff --git a/test/jdk/java/util/concurrent/tck/ConcurrentHashMapTest.java b/test/jdk/java/util/concurrent/tck/ConcurrentHashMapTest.java --- a/test/jdk/java/util/concurrent/tck/ConcurrentHashMapTest.java +++ b/test/jdk/java/util/concurrent/tck/ConcurrentHashMapTest.java @@ -55,8 +55,6 @@ class Implementation implements MapImplementation { public Class klazz() { return ConcurrentHashMap.class; } public Map emptyMap() { return new ConcurrentHashMap(); } - public Object makeKey(int i) { return i; } - public Object makeValue(int i) { return i; } public boolean isConcurrent() { return true; } public boolean permitsNullKeys() { return false; } public boolean permitsNullValues() { return false; } diff --git a/test/jdk/java/util/concurrent/tck/ConcurrentSkipListMapTest.java b/test/jdk/java/util/concurrent/tck/ConcurrentSkipListMapTest.java --- a/test/jdk/java/util/concurrent/tck/ConcurrentSkipListMapTest.java +++ b/test/jdk/java/util/concurrent/tck/ConcurrentSkipListMapTest.java @@ -54,9 +54,8 @@ class Implementation implements MapImplementation { public Class klazz() { return ConcurrentSkipListMap.class; } public Map emptyMap() { return new ConcurrentSkipListMap(); } - public Object makeKey(int i) { return i; } - public Object makeValue(int i) { return i; } public boolean isConcurrent() { return true; } + public boolean remappingFunctionCalledAtMostOnce() { return false; }; public boolean permitsNullKeys() { return false; } public boolean permitsNullValues() { return false; } public boolean supportsSetValue() { return false; } diff --git a/test/jdk/java/util/concurrent/tck/HashMapTest.java b/test/jdk/java/util/concurrent/tck/HashMapTest.java --- a/test/jdk/java/util/concurrent/tck/HashMapTest.java +++ b/test/jdk/java/util/concurrent/tck/HashMapTest.java @@ -46,8 +46,6 @@ class Implementation implements MapImplementation { public Class klazz() { return HashMap.class; } public Map emptyMap() { return new HashMap(); } - public Object makeKey(int i) { return i; } - public Object makeValue(int i) { return i; } public boolean isConcurrent() { return false; } public boolean permitsNullKeys() { return true; } public boolean permitsNullValues() { return true; } diff --git a/test/jdk/java/util/concurrent/tck/HashtableTest.java b/test/jdk/java/util/concurrent/tck/HashtableTest.java --- a/test/jdk/java/util/concurrent/tck/HashtableTest.java +++ b/test/jdk/java/util/concurrent/tck/HashtableTest.java @@ -44,8 +44,6 @@ class Implementation implements MapImplementation { public Class klazz() { return Hashtable.class; } public Map emptyMap() { return new Hashtable(); } - public Object makeKey(int i) { return i; } - public Object makeValue(int i) { return i; } public boolean isConcurrent() { return true; } public boolean permitsNullKeys() { return false; } public boolean permitsNullValues() { return false; } diff --git a/test/jdk/java/util/concurrent/tck/JSR166TestCase.java b/test/jdk/java/util/concurrent/tck/JSR166TestCase.java --- a/test/jdk/java/util/concurrent/tck/JSR166TestCase.java +++ b/test/jdk/java/util/concurrent/tck/JSR166TestCase.java @@ -731,6 +731,13 @@ /** * Returns a random element from given choices. */ + T chooseRandomly(List choices) { + return choices.get(ThreadLocalRandom.current().nextInt(choices.size())); + } + + /** + * Returns a random element from given choices. + */ T chooseRandomly(T... choices) { return choices[ThreadLocalRandom.current().nextInt(choices.length)]; } @@ -1799,7 +1806,7 @@ public int await() { try { - return super.await(2 * LONG_DELAY_MS, MILLISECONDS); + return super.await(LONGER_DELAY_MS, MILLISECONDS); } catch (TimeoutException timedOut) { throw new AssertionError("timed out"); } catch (Exception fail) { diff --git a/test/jdk/java/util/concurrent/tck/LinkedHashMapTest.java b/test/jdk/java/util/concurrent/tck/LinkedHashMapTest.java --- a/test/jdk/java/util/concurrent/tck/LinkedHashMapTest.java +++ b/test/jdk/java/util/concurrent/tck/LinkedHashMapTest.java @@ -46,8 +46,6 @@ class Implementation implements MapImplementation { public Class klazz() { return LinkedHashMap.class; } public Map emptyMap() { return new LinkedHashMap(); } - public Object makeKey(int i) { return i; } - public Object makeValue(int i) { return i; } public boolean isConcurrent() { return false; } public boolean permitsNullKeys() { return true; } public boolean permitsNullValues() { return true; } diff --git a/test/jdk/java/util/concurrent/tck/MapImplementation.java b/test/jdk/java/util/concurrent/tck/MapImplementation.java --- a/test/jdk/java/util/concurrent/tck/MapImplementation.java +++ b/test/jdk/java/util/concurrent/tck/MapImplementation.java @@ -40,9 +40,15 @@ public Class klazz(); /** Returns an empty map. */ public Map emptyMap(); - public Object makeKey(int i); - public Object makeValue(int i); + + // General purpose implementations can use Integers for key and value + default Object makeKey(int i) { return i; } + default Object makeValue(int i) { return i; } + default int keyToInt(Object key) { return (Integer) key; } + default int valueToInt(Object value) { return (Integer) value; } + public boolean isConcurrent(); + default boolean remappingFunctionCalledAtMostOnce() { return true; }; public boolean permitsNullKeys(); public boolean permitsNullValues(); public boolean supportsSetValue(); diff --git a/test/jdk/java/util/concurrent/tck/MapTest.java b/test/jdk/java/util/concurrent/tck/MapTest.java --- a/test/jdk/java/util/concurrent/tck/MapTest.java +++ b/test/jdk/java/util/concurrent/tck/MapTest.java @@ -32,13 +32,17 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -import junit.framework.Test; - import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiFunction; + +import junit.framework.Test; /** * Contains tests applicable to all Map implementations. @@ -227,6 +231,71 @@ assertTrue(clone.isEmpty()); } + /** + * Concurrent access by compute methods behaves as expected + */ + public void testConcurrentAccess() throws Throwable { + final Map map = impl.emptyMap(); + final long testDurationMillis = expensiveTests ? 1000 : 2; + final int nTasks = impl.isConcurrent() + ? ThreadLocalRandom.current().nextInt(1, 10) + : 1; + final AtomicBoolean done = new AtomicBoolean(false); + final boolean remappingFunctionCalledAtMostOnce + = impl.remappingFunctionCalledAtMostOnce(); + final List futures = new ArrayList<>(); + final AtomicLong expectedSum = new AtomicLong(0); + final Action[] tasks = { + // repeatedly increment values using compute() + () -> { + long[] invocations = new long[2]; + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + BiFunction incValue = (k, v) -> { + invocations[1]++; + int vi = (v == null) ? 1 : impl.valueToInt(v) + 1; + return impl.makeValue(vi); + }; + while (!done.getAcquire()) { + invocations[0]++; + Object key = impl.makeKey(3 * rnd.nextInt(10)); + map.compute(key, incValue); + } + if (remappingFunctionCalledAtMostOnce) + assertEquals(invocations[0], invocations[1]); + expectedSum.getAndAdd(invocations[0]); + }, + // repeatedly increment values using computeIfPresent() + () -> { + long[] invocations = new long[2]; + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + BiFunction incValue = (k, v) -> { + invocations[1]++; + int vi = impl.valueToInt(v) + 1; + return impl.makeValue(vi); + }; + while (!done.getAcquire()) { + Object key = impl.makeKey(3 * rnd.nextInt(10)); + if (map.computeIfPresent(key, incValue) != null) + invocations[0]++; + } + if (remappingFunctionCalledAtMostOnce) + assertEquals(invocations[0], invocations[1]); + expectedSum.getAndAdd(invocations[0]); + }, + }; + for (int i = nTasks; i--> 0; ) { + Action task = chooseRandomly(tasks); + futures.add(CompletableFuture.runAsync(checkedRunnable(task))); + } + Thread.sleep(testDurationMillis); + done.setRelease(true); + for (var future : futures) + checkTimedGet(future, null); + + long sum = map.values().stream().mapToLong(x -> (int) x).sum(); + assertEquals(expectedSum.get(), sum); + } + // public void testFailsIntentionallyForDebugging() { // fail(impl.klazz().getSimpleName()); // } diff --git a/test/jdk/java/util/concurrent/tck/StampedLockTest.java b/test/jdk/java/util/concurrent/tck/StampedLockTest.java --- a/test/jdk/java/util/concurrent/tck/StampedLockTest.java +++ b/test/jdk/java/util/concurrent/tck/StampedLockTest.java @@ -42,12 +42,17 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.StampedLock; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Function; import junit.framework.Test; @@ -102,56 +107,51 @@ } List lockLockers(Lock lock) { - List lockers = new ArrayList<>(); - lockers.add(() -> lock.lock()); - lockers.add(() -> lock.lockInterruptibly()); - lockers.add(() -> lock.tryLock()); - lockers.add(() -> lock.tryLock(Long.MIN_VALUE, DAYS)); - lockers.add(() -> lock.tryLock(0L, DAYS)); - lockers.add(() -> lock.tryLock(Long.MAX_VALUE, DAYS)); - return lockers; + return List.of( + () -> lock.lock(), + () -> lock.lockInterruptibly(), + () -> lock.tryLock(), + () -> lock.tryLock(Long.MIN_VALUE, DAYS), + () -> lock.tryLock(0L, DAYS), + () -> lock.tryLock(Long.MAX_VALUE, DAYS)); } List> readLockers() { - List> readLockers = new ArrayList<>(); - readLockers.add(sl -> sl.readLock()); - readLockers.add(sl -> sl.tryReadLock()); - readLockers.add(sl -> readLockInterruptiblyUninterrupted(sl)); - readLockers.add(sl -> tryReadLockUninterrupted(sl, Long.MIN_VALUE, DAYS)); - readLockers.add(sl -> tryReadLockUninterrupted(sl, 0L, DAYS)); - readLockers.add(sl -> sl.tryConvertToReadLock(sl.tryOptimisticRead())); - return readLockers; + return List.of( + sl -> sl.readLock(), + sl -> sl.tryReadLock(), + sl -> readLockInterruptiblyUninterrupted(sl), + sl -> tryReadLockUninterrupted(sl, Long.MIN_VALUE, DAYS), + sl -> tryReadLockUninterrupted(sl, 0L, DAYS), + sl -> sl.tryConvertToReadLock(sl.tryOptimisticRead())); } List> readUnlockers() { - List> readUnlockers = new ArrayList<>(); - readUnlockers.add((sl, stamp) -> sl.unlockRead(stamp)); - readUnlockers.add((sl, stamp) -> assertTrue(sl.tryUnlockRead())); - readUnlockers.add((sl, stamp) -> sl.asReadLock().unlock()); - readUnlockers.add((sl, stamp) -> sl.unlock(stamp)); - readUnlockers.add((sl, stamp) -> assertValid(sl, sl.tryConvertToOptimisticRead(stamp))); - return readUnlockers; + return List.of( + (sl, stamp) -> sl.unlockRead(stamp), + (sl, stamp) -> assertTrue(sl.tryUnlockRead()), + (sl, stamp) -> sl.asReadLock().unlock(), + (sl, stamp) -> sl.unlock(stamp), + (sl, stamp) -> assertValid(sl, sl.tryConvertToOptimisticRead(stamp))); } List> writeLockers() { - List> writeLockers = new ArrayList<>(); - writeLockers.add(sl -> sl.writeLock()); - writeLockers.add(sl -> sl.tryWriteLock()); - writeLockers.add(sl -> writeLockInterruptiblyUninterrupted(sl)); - writeLockers.add(sl -> tryWriteLockUninterrupted(sl, Long.MIN_VALUE, DAYS)); - writeLockers.add(sl -> tryWriteLockUninterrupted(sl, 0L, DAYS)); - writeLockers.add(sl -> sl.tryConvertToWriteLock(sl.tryOptimisticRead())); - return writeLockers; + return List.of( + sl -> sl.writeLock(), + sl -> sl.tryWriteLock(), + sl -> writeLockInterruptiblyUninterrupted(sl), + sl -> tryWriteLockUninterrupted(sl, Long.MIN_VALUE, DAYS), + sl -> tryWriteLockUninterrupted(sl, 0L, DAYS), + sl -> sl.tryConvertToWriteLock(sl.tryOptimisticRead())); } List> writeUnlockers() { - List> writeUnlockers = new ArrayList<>(); - writeUnlockers.add((sl, stamp) -> sl.unlockWrite(stamp)); - writeUnlockers.add((sl, stamp) -> assertTrue(sl.tryUnlockWrite())); - writeUnlockers.add((sl, stamp) -> sl.asWriteLock().unlock()); - writeUnlockers.add((sl, stamp) -> sl.unlock(stamp)); - writeUnlockers.add((sl, stamp) -> assertValid(sl, sl.tryConvertToOptimisticRead(stamp))); - return writeUnlockers; + return List.of( + (sl, stamp) -> sl.unlockWrite(stamp), + (sl, stamp) -> assertTrue(sl.tryUnlockWrite()), + (sl, stamp) -> sl.asWriteLock().unlock(), + (sl, stamp) -> sl.unlock(stamp), + (sl, stamp) -> assertValid(sl, sl.tryConvertToOptimisticRead(stamp))); } /** @@ -1413,4 +1413,113 @@ } } + /** + * Multiple threads repeatedly contend for the same lock. + */ + public void testConcurrentAccess() throws Exception { + final StampedLock sl = new StampedLock(); + final Lock wl = sl.asWriteLock(); + final Lock rl = sl.asReadLock(); + final long testDurationMillis = expensiveTests ? 1000 : 2; + final int nTasks = ThreadLocalRandom.current().nextInt(1, 10); + final AtomicBoolean done = new AtomicBoolean(false); + final List futures = new ArrayList<>(); + final List> stampedWriteLockers = List.of( + () -> sl.writeLock(), + () -> writeLockInterruptiblyUninterrupted(sl), + () -> tryWriteLockUninterrupted(sl, LONG_DELAY_MS, MILLISECONDS), + () -> { + long stamp; + do { stamp = sl.tryConvertToWriteLock(sl.tryOptimisticRead()); } + while (stamp == 0L); + return stamp; + }, + () -> { + long stamp; + do { stamp = sl.tryWriteLock(); } while (stamp == 0L); + return stamp; + }, + () -> { + long stamp; + do { stamp = sl.tryWriteLock(0L, DAYS); } while (stamp == 0L); + return stamp; + }); + final List> stampedReadLockers = List.of( + () -> sl.readLock(), + () -> readLockInterruptiblyUninterrupted(sl), + () -> tryReadLockUninterrupted(sl, LONG_DELAY_MS, MILLISECONDS), + () -> { + long stamp; + do { stamp = sl.tryConvertToReadLock(sl.tryOptimisticRead()); } + while (stamp == 0L); + return stamp; + }, + () -> { + long stamp; + do { stamp = sl.tryReadLock(); } while (stamp == 0L); + return stamp; + }, + () -> { + long stamp; + do { stamp = sl.tryReadLock(0L, DAYS); } while (stamp == 0L); + return stamp; + }); + final List> stampedWriteUnlockers = List.of( + stamp -> sl.unlockWrite(stamp), + stamp -> sl.unlock(stamp), + stamp -> assertTrue(sl.tryUnlockWrite()), + stamp -> wl.unlock(), + stamp -> sl.tryConvertToOptimisticRead(stamp)); + final List> stampedReadUnlockers = List.of( + stamp -> sl.unlockRead(stamp), + stamp -> sl.unlock(stamp), + stamp -> assertTrue(sl.tryUnlockRead()), + stamp -> rl.unlock(), + stamp -> sl.tryConvertToOptimisticRead(stamp)); + final Action writer = () -> { + // repeatedly acquires write lock + var locker = chooseRandomly(stampedWriteLockers); + var unlocker = chooseRandomly(stampedWriteUnlockers); + while (!done.getAcquire()) { + long stamp = locker.call(); + try { + assertTrue(isWriteLockStamp(stamp)); + assertTrue(sl.isWriteLocked()); + assertFalse(isReadLockStamp(stamp)); + assertFalse(sl.isReadLocked()); + assertEquals(0, sl.getReadLockCount()); + assertTrue(sl.validate(stamp)); + } finally { + unlocker.accept(stamp); + } + } + }; + final Action reader = () -> { + // repeatedly acquires read lock + var locker = chooseRandomly(stampedReadLockers); + var unlocker = chooseRandomly(stampedReadUnlockers); + while (!done.getAcquire()) { + long stamp = locker.call(); + try { + assertFalse(isWriteLockStamp(stamp)); + assertFalse(sl.isWriteLocked()); + assertTrue(isReadLockStamp(stamp)); + assertTrue(sl.isReadLocked()); + assertTrue(sl.getReadLockCount() > 0); + assertTrue(sl.validate(stamp)); + } finally { + unlocker.accept(stamp); + } + } + }; + for (int i = nTasks; i--> 0; ) { + Action task = chooseRandomly(writer, reader); + futures.add(CompletableFuture.runAsync(checkedRunnable(task))); + } + Thread.sleep(testDurationMillis); + done.setRelease(true); + for (var future : futures) + checkTimedGet(future, null); + } + } diff --git a/test/jdk/java/util/concurrent/tck/TreeMapTest.java b/test/jdk/java/util/concurrent/tck/TreeMapTest.java --- a/test/jdk/java/util/concurrent/tck/TreeMapTest.java +++ b/test/jdk/java/util/concurrent/tck/TreeMapTest.java @@ -53,8 +53,6 @@ class Implementation implements MapImplementation { public Class klazz() { return TreeMap.class; } public Map emptyMap() { return new TreeMap(); } - public Object makeKey(int i) { return i; } - public Object makeValue(int i) { return i; } public boolean isConcurrent() { return false; } public boolean permitsNullKeys() { return false; } public boolean permitsNullValues() { return true; }